diff --git a/build.sbt b/build.sbt index 8d8a07f330..7427e0c190 100644 --- a/build.sbt +++ b/build.sbt @@ -166,6 +166,7 @@ lazy val root: Project = Project( ).settings( unidocProjectFilter in (ScalaUnidoc, unidoc) := inAnyProject -- inProjects(scioRepl) -- inProjects(scioSchemas) -- inProjects(scioExamples), + run <<= run in Compile in scioRepl, aggregate in assembly := false ).aggregate( scioCore, @@ -344,6 +345,16 @@ lazy val scioRepl: Project = Project( Nil ) ) +).settings( + mainClass in (Compile,run) := { + if (scalaVersion.value.startsWith("2.10")) { + throw new UnsupportedOperationException( + "\n\n\tERROR: Can't start Scio REPL in SBT for scala 2.10.x. " + + "Upgrade to 2.11.x. or build REPL assembly jar.\n" + + "\tMore info https://github.com/spotify/scio/wiki/Scio-REPL\n\n") + } + Some("com.spotify.scio.repl.ScioShell") + } ).settings( assemblyJarName in assembly := s"scio-repl-${version.value}.jar" ).dependsOn( diff --git a/scio-repl/src/main/scala/com/spotify/scio/repl/ScioShell.scala b/scio-repl/src/main/scala/com/spotify/scio/repl/ScioShell.scala index 4426e7d40c..b3feb06324 100644 --- a/scio-repl/src/main/scala/com/spotify/scio/repl/ScioShell.scala +++ b/scio-repl/src/main/scala/com/spotify/scio/repl/ScioShell.scala @@ -17,6 +17,8 @@ package com.spotify.scio.repl +import scala.reflect.io.File +import scala.tools.nsc.util.ClassPath import scala.tools.nsc.{GenericRunnerCommand, MainGenericRunner} /** @@ -35,19 +37,41 @@ trait BaseScioShell extends MainGenericRunner { * @param args passed from the command line * @return `true` if execution was successful, `false` otherwise */ + // scalastyle:off method.length override def process(args: Array[String]): Boolean = { // Process command line arguments into a settings object, and use that to start the REPL. // We ignore params we don't care about - hence error function is empty val command = new GenericRunnerCommand(args.toList, _ => ()) - // if running from the assembly, need to explicitly tell it to use java classpath - command.settings.usejavacp.value = true - command.settings.classpath.append(System.getProperty("java.class.path")) + // For scala 2.10 - usejavacp + if (scala.util.Properties.versionString.contains("2.10.")) { + command.settings.classpath.append(System.getProperty("java.class.path")) + command.settings.usejavacp.value = true + } + + def classLoaderURLs(cl: ClassLoader): Array[java.net.URL] = cl match { + case null => Array() + case u: java.net.URLClassLoader => u.getURLs ++ classLoaderURLs(cl.getParent) + case _ => classLoaderURLs(cl.getParent) + } + + classLoaderURLs(Thread.currentThread().getContextClassLoader) + .foreach(u => command.settings.classpath.append(u.getPath)) + + // We have to make sure that scala macros are expandable. paradise plugin has to be added to + // -Xplugin paths. In case of assembly - paradise is included in assembly jar - thus we add + // itself to -Xplugin. If shell is started from sbt or classpath, paradise jar has to be in + // classpath, we find it and add it to -Xplugin. // Repl assembly includes paradise's scalac-plugin.xml - required for BigQuery macro + // There should be no harm if we keep this for sbt launch. val thisJar = this.getClass.getProtectionDomain.getCodeSource.getLocation.getPath command.settings.plugin.appendToValue(thisJar) + ClassPath.split(command.settings.classpath.value) + .find(File(_).name.startsWith("paradise_")) + .foreach(command.settings.plugin.appendToValue) + // Useful settings for for debugging, dumping class files etc: /* command.settings.debug.value = true command.settings.Yreploutdir.tryToSet(List("")) @@ -56,7 +80,9 @@ trait BaseScioShell extends MainGenericRunner { // Force the repl to be synchronous, so all cmds are executed in the same thread command.settings.Yreplsync.value = true - val scioClassLoader = new ScioReplClassLoader(command.settings.classpathURLs.toArray, + val scioClassLoader = new ScioReplClassLoader( + command.settings.classpathURLs.toArray ++ + classLoaderURLs(Thread.currentThread().getContextClassLoader), null, Thread.currentThread.getContextClassLoader) @@ -65,10 +91,12 @@ trait BaseScioShell extends MainGenericRunner { // Set classloader chain - expose top level abstract class loader down // the chain to allow for readObject and latestUserDefinedLoader + // See https://gist.github.com/harrah/404272 command.settings.embeddedDefaults(scioClassLoader) repl.process(command.settings) } + // scalastyle:on method.length /** Runs an instance of the shell. */ def main(args: Array[String]) {