sbt/project/Scripted.scala

166 lines
5.7 KiB
Scala
Raw Normal View History

2018-07-31 02:28:42 +02:00
package local
2018-01-24 14:42:18 +01:00
import java.lang.reflect.InvocationTargetException
2014-12-18 05:38:10 +01:00
import sbt._
Upgrade the build to sbt 1.0.0-M5 Some plugins remain commented out, for now. sbt-doge is no longer needed because a variant of it has been folded into sbt 1. For some reason scripted requires src/doc jars of sbt, so switch back to using `publishAll` rather than `publishLocalBinAll`. :( Also, the sys.prop change in scripted is to force log4j2 to not use a thread context classloader, and avoid the following: ERROR StatusLogger Unable to create custom ContextSelector. Falling back to default. java.lang.ClassCastException: Cannot cast org.apache.logging.log4j.core.async.AsyncLoggerContextSelector to org.apache.logging.log4j.core.selector.ContextSelector at java.lang.Class.cast(Class.java:3369) at org.apache.logging.log4j.util.LoaderUtil.newCheckedInstanceOf(LoaderUtil.java:201) at org.apache.logging.log4j.util.LoaderUtil.newCheckedInstanceOfProperty(LoaderUtil.java:226) at org.apache.logging.log4j.core.impl.Log4jContextFactory.createContextSelector(Log4jContextFactory.java:97) at org.apache.logging.log4j.core.impl.Log4jContextFactory.<init>(Log4jContextFactory.java:58) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.lang.Class.newInstance(Class.java:442) at org.apache.logging.log4j.LogManager.<clinit>(LogManager.java:94) at org.apache.logging.log4j.spi.ThreadContextMapFactory.createThreadContextMap(ThreadContextMapFactory.java:73) at org.apache.logging.log4j.ThreadContext.init(ThreadContext.java:223) at org.apache.logging.log4j.ThreadContext.<clinit>(ThreadContext.java:202) at org.apache.logging.log4j.core.impl.ContextDataInjectorFactory.createDefaultInjector(ContextDataInjectorFactory.java:83) at org.apache.logging.log4j.core.impl.ContextDataInjectorFactory.createInjector(ContextDataInjectorFactory.java:67) at org.apache.logging.log4j.core.lookup.ContextMapLookup.<init>(ContextMapLookup.java:34) at org.apache.logging.log4j.core.lookup.Interpolator.<init>(Interpolator.java:117) at org.apache.logging.log4j.core.config.AbstractConfiguration.<init>(AbstractConfiguration.java:125) at org.apache.logging.log4j.core.config.DefaultConfiguration.<init>(DefaultConfiguration.java:46) at org.apache.logging.log4j.core.layout.PatternLayout$Builder.build(PatternLayout.java:650) at org.apache.logging.log4j.core.layout.PatternLayout.createDefaultLayout(PatternLayout.java:487) at sbt.internal.util.ConsoleAppender.<init>(ConsoleAppender.scala:245) at sbt.internal.util.ConsoleAppender$.apply(ConsoleAppender.scala:196) at sbt.internal.util.ConsoleLogger.<init>(ConsoleAppender.scala:42) at sbt.internal.util.ConsoleLogger$.apply(ConsoleAppender.scala:34) at sbt.test.ScriptedRunner.run(ScriptedTests.scala:221) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at Scripted$.doScripted(Scripted.scala:125) at $0483e89d182e9d240274$.$anonfun$scriptedTask$5(build.sbt:301)
2017-05-03 16:52:36 +02:00
import sbt.internal.inc.ScalaInstance
2018-01-24 14:42:18 +01:00
import sbt.internal.inc.classpath.{ ClasspathUtilities, FilteredLoader }
2015-07-10 11:53:48 +02:00
2018-07-31 02:28:42 +02:00
object LocalScriptedPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
2018-01-24 14:42:18 +01:00
object autoImport extends ScriptedKeys
}
trait ScriptedKeys {
val publishLocalBinAll = taskKey[Unit]("")
val scriptedUnpublished = inputKey[Unit](
"Execute scripted without publishing sbt first. " +
"Saves you some time when only your test has changed"
)
val scriptedSource = settingKey[File]("")
val scriptedPrescripted = taskKey[File => Unit]("")
}
2015-01-13 04:01:16 +01:00
object Scripted {
// This is to workaround https://github.com/sbt/io/issues/110
sys.props.put("jna.nosys", "true")
2018-01-17 16:19:21 +01:00
val RepoOverrideTest = config("repoOverrideTest") extend Compile
2014-12-18 05:38:10 +01:00
import sbt.complete._
2018-01-24 14:42:18 +01:00
2014-12-18 05:38:10 +01:00
// Paging, 1-index based.
2018-01-24 14:42:18 +01:00
final case class ScriptedTestPage(page: Int, total: Int)
// FIXME: Duplicated with ScriptedPlugin.scriptedParser, this can be
// avoided once we upgrade build.properties to 0.13.14
2017-04-21 09:14:31 +02:00
def scriptedParser(scriptedBase: File): Parser[Seq[String]] = {
2018-01-24 14:42:18 +01:00
import DefaultParsers._
2017-04-21 09:14:31 +02:00
val scriptedFiles: NameFilter = ("test": NameFilter) | "pending"
val pairs = (scriptedBase * AllPassFilter * AllPassFilter * scriptedFiles).get map {
(f: File) =>
2014-12-18 05:38:10 +01:00
val p = f.getParentFile
(p.getParentFile.getName, p.getName)
2017-04-21 09:14:31 +02:00
}
2018-01-24 14:42:18 +01:00
val pairMap = pairs.groupBy(_._1).mapValues(_.map(_._2).toSet)
2014-12-18 05:38:10 +01:00
2017-04-21 09:14:31 +02:00
val id = charClass(c => !c.isWhitespace && c != '/').+.string
2018-01-24 14:42:18 +01:00
val groupP = token(id.examples(pairMap.keySet)) <~ token('/')
2014-12-18 05:38:10 +01:00
2017-04-21 09:14:31 +02:00
// A parser for page definitions
val pageNumber = (NatBasic & not('0', "zero page number")).flatMap { i =>
if (i <= pairs.size) Parser.success(i)
else Parser.failure(s"$i exceeds the number of tests (${pairs.size})")
}
val pageP: Parser[ScriptedTestPage] = ("*" ~> pageNumber ~ ("of" ~> pageNumber)) flatMap {
case (page, total) if page <= total => success(ScriptedTestPage(page, total))
case (page, total) => failure(s"Page $page was greater than $total")
2017-04-21 09:14:31 +02:00
}
2018-01-24 14:42:18 +01:00
2017-04-21 09:14:31 +02:00
// Grabs the filenames from a given test group in the current page definition.
def pagedFilenames(group: String, page: ScriptedTestPage): Seq[String] = {
val files = pairMap.get(group).toSeq.flatten.sortBy(_.toLowerCase)
2019-08-08 19:18:43 +02:00
val pageSize = if (page.total == 0) files.size else files.size / page.total
2017-04-21 09:14:31 +02:00
// The last page may loose some values, so we explicitly keep them
val dropped = files.drop(pageSize * (page.page - 1))
if (page.page == page.total) dropped
else dropped.take(pageSize)
}
2018-01-24 14:42:18 +01:00
2017-04-21 09:14:31 +02:00
def nameP(group: String) = {
token("*".id | id.examples(pairMap.getOrElse(group, Set.empty[String])))
2014-12-18 05:38:10 +01:00
}
2018-01-24 14:42:18 +01:00
2017-04-21 09:14:31 +02:00
val PagedIds: Parser[Seq[String]] =
for {
group <- groupP
page <- pageP
files = pagedFilenames(group, page)
// TODO - Fail the parser if we don't have enough files for the given page size
//if !files.isEmpty
2018-01-24 14:42:18 +01:00
} yield files map (f => s"$group/$f")
2017-04-21 09:14:31 +02:00
val testID = (for (group <- groupP; name <- nameP(group)) yield (group, name))
val testIdAsGroup = matched(testID) map (test => Seq(test))
2018-01-24 14:42:18 +01:00
2017-04-21 09:14:31 +02:00
//(token(Space) ~> matched(testID)).*
(token(Space) ~> (PagedIds | testIdAsGroup)).* map (_.flatten)
}
2014-12-18 05:38:10 +01:00
2018-01-24 14:42:18 +01:00
def doScripted(
scriptedSbtInstance: ScalaInstance,
sourcePath: File,
bufferLog: Boolean,
args: Seq[String],
prescripted: File => Unit,
launchOpts: Seq[String],
Explicitly set scripted and server test classpath This commit makes it so that the scalaVersion, sbtVersion and classpath are always passed in as parameters to any method that creates an sbt server -- either for scripted or for the sbt server tests. By making that change, I was able to change the implementation of scripted in the sbt project to use publishLocalBin instead of publishLocal. This makes the scripted tests start much faster (doc alone can easily take 30 second) with messing with the build to exclude slow tasks from publishLocal. As part of this change, I removed the test dependency on scriptedSbtRedux for sbtProj and instead had scriptedSbtRedux depend on sbtProj. This allowed me to remove some messy LocalProject logic in the resourceGenerators for scriptedSbtReduxProj. I also had to remove a number of imports in the scriptedSbtReduxProj because the definitions available in the sbt package object became available. I also removed the dependency on sbt-buildinfo and instead pass the values from the build into test classes using scalatest properties. I ran into a number of minor issues with the build info plugin, namely that I couldn't get fullClasspathAsJars to reliably run as a BuildInfo key. It also is somewhat more clear to me to just rely on the built in scalatest functionality. The big drawback is that the scalatest properties can only be strings, but that restriction isn't really a problem here (strangely the TestData structure has a field configMap which is effectively Map[String, Any] but Any is actually always String given how the TestData is created as part of framework initialization. Since scripted no longer publishes, scriptedUnpublished is now effectively an alias for scripted. To get publishLocalBin working, I had to copy private code from IvyXml.scala into PublishBinPlugin. Once we publish a new version of sbt, we can remove the copied code and invoke IvyXml.makeIvyXmlBefore directly.
2020-01-12 04:52:36 +01:00
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File],
logger: Logger
2018-01-24 14:42:18 +01:00
): Unit = {
logger.info(s"About to run tests: ${args.mkString("\n * ", "\n * ", "\n")}")
logger.info("")
2018-01-24 14:42:18 +01:00
2017-11-16 15:09:25 +01:00
// Force Log4J to not use a thread context classloader otherwise it throws a CCE
sys.props(org.apache.logging.log4j.util.LoaderUtil.IGNORE_TCCL_PROPERTY) = "true"
2018-01-24 14:42:18 +01:00
val noJLine = new FilteredLoader(scriptedSbtInstance.loader, "jline." :: Nil)
Explicitly set scripted and server test classpath This commit makes it so that the scalaVersion, sbtVersion and classpath are always passed in as parameters to any method that creates an sbt server -- either for scripted or for the sbt server tests. By making that change, I was able to change the implementation of scripted in the sbt project to use publishLocalBin instead of publishLocal. This makes the scripted tests start much faster (doc alone can easily take 30 second) with messing with the build to exclude slow tasks from publishLocal. As part of this change, I removed the test dependency on scriptedSbtRedux for sbtProj and instead had scriptedSbtRedux depend on sbtProj. This allowed me to remove some messy LocalProject logic in the resourceGenerators for scriptedSbtReduxProj. I also had to remove a number of imports in the scriptedSbtReduxProj because the definitions available in the sbt package object became available. I also removed the dependency on sbt-buildinfo and instead pass the values from the build into test classes using scalatest properties. I ran into a number of minor issues with the build info plugin, namely that I couldn't get fullClasspathAsJars to reliably run as a BuildInfo key. It also is somewhat more clear to me to just rely on the built in scalatest functionality. The big drawback is that the scalatest properties can only be strings, but that restriction isn't really a problem here (strangely the TestData structure has a field configMap which is effectively Map[String, Any] but Any is actually always String given how the TestData is created as part of framework initialization. Since scripted no longer publishes, scriptedUnpublished is now effectively an alias for scripted. To get publishLocalBin working, I had to copy private code from IvyXml.scala into PublishBinPlugin. Once we publish a new version of sbt, we can remove the copied code and invoke IvyXml.makeIvyXmlBefore directly.
2020-01-12 04:52:36 +01:00
val loader = ClasspathUtilities.toLoader(classpath, noJLine)
val bridgeClass = Class.forName("sbt.scriptedtest.ScriptedRunner", true, loader)
2018-01-24 14:42:18 +01:00
// Interface to cross class loader
type SbtScriptedRunner = {
def runInParallel(
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Array[String],
launchOpts: Array[String],
prescripted: java.util.List[File],
Explicitly set scripted and server test classpath This commit makes it so that the scalaVersion, sbtVersion and classpath are always passed in as parameters to any method that creates an sbt server -- either for scripted or for the sbt server tests. By making that change, I was able to change the implementation of scripted in the sbt project to use publishLocalBin instead of publishLocal. This makes the scripted tests start much faster (doc alone can easily take 30 second) with messing with the build to exclude slow tasks from publishLocal. As part of this change, I removed the test dependency on scriptedSbtRedux for sbtProj and instead had scriptedSbtRedux depend on sbtProj. This allowed me to remove some messy LocalProject logic in the resourceGenerators for scriptedSbtReduxProj. I also had to remove a number of imports in the scriptedSbtReduxProj because the definitions available in the sbt package object became available. I also removed the dependency on sbt-buildinfo and instead pass the values from the build into test classes using scalatest properties. I ran into a number of minor issues with the build info plugin, namely that I couldn't get fullClasspathAsJars to reliably run as a BuildInfo key. It also is somewhat more clear to me to just rely on the built in scalatest functionality. The big drawback is that the scalatest properties can only be strings, but that restriction isn't really a problem here (strangely the TestData structure has a field configMap which is effectively Map[String, Any] but Any is actually always String given how the TestData is created as part of framework initialization. Since scripted no longer publishes, scriptedUnpublished is now effectively an alias for scripted. To get publishLocalBin working, I had to copy private code from IvyXml.scala into PublishBinPlugin. Once we publish a new version of sbt, we can remove the copied code and invoke IvyXml.makeIvyXmlBefore directly.
2020-01-12 04:52:36 +01:00
scalaVersion: String,
sbtVersion: String,
2020-03-18 21:28:51 +01:00
classpath: Array[File],
instances: Int
2018-01-24 14:42:18 +01:00
): Unit
}
val initLoader = Thread.currentThread.getContextClassLoader
try {
Thread.currentThread.setContextClassLoader(loader)
val bridge =
bridgeClass.getDeclaredConstructor().newInstance().asInstanceOf[SbtScriptedRunner]
try {
// Using java.util.List to encode File => Unit.
val callback = new java.util.AbstractList[File] {
override def add(x: File): Boolean = { prescripted(x); false }
def get(x: Int): sbt.File = ???
def size(): Int = 0
}
val instances: Int = (System.getProperty("sbt.scripted.parallel.instances") match {
case null => 1
case i => scala.util.Try(i.toInt).getOrElse(1)
}) match {
case i if i > 0 => i
case _ => 1
}
import scala.language.reflectiveCalls
bridge.runInParallel(
sourcePath,
bufferLog,
args.toArray,
launchOpts.toArray,
callback,
Explicitly set scripted and server test classpath This commit makes it so that the scalaVersion, sbtVersion and classpath are always passed in as parameters to any method that creates an sbt server -- either for scripted or for the sbt server tests. By making that change, I was able to change the implementation of scripted in the sbt project to use publishLocalBin instead of publishLocal. This makes the scripted tests start much faster (doc alone can easily take 30 second) with messing with the build to exclude slow tasks from publishLocal. As part of this change, I removed the test dependency on scriptedSbtRedux for sbtProj and instead had scriptedSbtRedux depend on sbtProj. This allowed me to remove some messy LocalProject logic in the resourceGenerators for scriptedSbtReduxProj. I also had to remove a number of imports in the scriptedSbtReduxProj because the definitions available in the sbt package object became available. I also removed the dependency on sbt-buildinfo and instead pass the values from the build into test classes using scalatest properties. I ran into a number of minor issues with the build info plugin, namely that I couldn't get fullClasspathAsJars to reliably run as a BuildInfo key. It also is somewhat more clear to me to just rely on the built in scalatest functionality. The big drawback is that the scalatest properties can only be strings, but that restriction isn't really a problem here (strangely the TestData structure has a field configMap which is effectively Map[String, Any] but Any is actually always String given how the TestData is created as part of framework initialization. Since scripted no longer publishes, scriptedUnpublished is now effectively an alias for scripted. To get publishLocalBin working, I had to copy private code from IvyXml.scala into PublishBinPlugin. Once we publish a new version of sbt, we can remove the copied code and invoke IvyXml.makeIvyXmlBefore directly.
2020-01-12 04:52:36 +01:00
scalaVersion,
sbtVersion,
2020-03-18 21:28:51 +01:00
classpath.toArray,
instances
)
} catch { case ite: InvocationTargetException => throw ite.getCause }
} finally {
Thread.currentThread.setContextClassLoader(initLoader)
}
2014-12-18 05:38:10 +01:00
}
}