mirror of https://github.com/sbt/sbt.git
Discover all sbt-related modules (not just Plugin) and write names to resources for use from binaries.
This commit is contained in:
parent
7a38338509
commit
09c76f29a3
|
|
@ -179,7 +179,7 @@ object Defaults extends BuildCommon
|
|||
unmanagedResources <<= collectFiles(unmanagedResourceDirectories, includeFilter in unmanagedResources, excludeFilter in unmanagedResources),
|
||||
watchSources in ConfigGlobal ++= unmanagedResources.value,
|
||||
resourceGenerators :== Nil,
|
||||
resourceGenerators <+= (definedSbtPlugins, resourceManaged) map writePluginsDescriptor,
|
||||
resourceGenerators <+= (discoveredSbtPlugins, resourceManaged) map PluginDiscovery.writeDescriptors,
|
||||
managedResources <<= generate(resourceGenerators),
|
||||
resources <<= Classpaths.concat(managedResources, unmanagedResources)
|
||||
)
|
||||
|
|
@ -233,6 +233,7 @@ object Defaults extends BuildCommon
|
|||
consoleQuick <<= consoleQuickTask,
|
||||
discoveredMainClasses <<= compile map discoverMainClasses storeAs discoveredMainClasses triggeredBy compile,
|
||||
definedSbtPlugins <<= discoverPlugins,
|
||||
discoveredSbtPlugins <<= discoverSbtPluginNames,
|
||||
inTask(run)(runnerTask :: Nil).head,
|
||||
selectMainClass := mainClass.value orElse selectRunMain(discoveredMainClasses.value),
|
||||
mainClass in run := (selectMainClass in run).value,
|
||||
|
|
@ -764,27 +765,21 @@ object Defaults extends BuildCommon
|
|||
|
||||
def sbtPluginExtra(m: ModuleID, sbtV: String, scalaV: String): ModuleID =
|
||||
m.extra(CustomPomParser.SbtVersionKey -> sbtV, CustomPomParser.ScalaVersionKey -> scalaV).copy(crossVersion = CrossVersion.Disabled)
|
||||
|
||||
@deprecated("Use PluginDiscovery.writeDescriptor.", "0.13.2")
|
||||
def writePluginsDescriptor(plugins: Set[String], dir: File): Seq[File] =
|
||||
{
|
||||
val descriptor: File = dir / "sbt" / "sbt.plugins"
|
||||
if(plugins.isEmpty)
|
||||
{
|
||||
IO.delete(descriptor)
|
||||
Nil
|
||||
}
|
||||
else
|
||||
{
|
||||
IO.writeLines(descriptor, plugins.toSeq.sorted)
|
||||
descriptor :: Nil
|
||||
}
|
||||
PluginDiscovery.writeDescriptor(plugins.toSeq, dir, PluginDiscovery.Paths.Plugins).toList
|
||||
|
||||
def discoverSbtPluginNames: Initialize[Task[PluginDiscovery.DiscoveredNames]] = Def.task {
|
||||
if(sbtPlugin.value) PluginDiscovery.discoverSourceAll(compile.value) else PluginDiscovery.emptyDiscoveredNames
|
||||
}
|
||||
|
||||
@deprecated("Use discoverSbtPluginNames.", "0.13.2")
|
||||
def discoverPlugins: Initialize[Task[Set[String]]] = (compile, sbtPlugin, streams) map { (analysis, isPlugin, s) => if(isPlugin) discoverSbtPlugins(analysis, s.log) else Set.empty }
|
||||
|
||||
@deprecated("Use PluginDiscovery.sourceModuleNames[Plugin].", "0.13.2")
|
||||
def discoverSbtPlugins(analysis: inc.Analysis, log: Logger): Set[String] =
|
||||
{
|
||||
val pluginClass = classOf[Plugin].getName
|
||||
val discovery = Discovery(Set(pluginClass), Set.empty)( Tests allDefs analysis )
|
||||
discovery collect { case (df, disc) if (disc.baseClasses contains pluginClass) && disc.isModule => df.name } toSet;
|
||||
}
|
||||
PluginDiscovery.sourceModuleNames(analysis, classOf[Plugin].getName).toSet
|
||||
|
||||
def copyResourcesTask =
|
||||
(classDirectory, resources, resourceDirectories, streams) map { (target, resrcs, dirs, s) =>
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ object Keys
|
|||
val crossVersion = SettingKey[CrossVersion]("cross-version", "Configures handling of the Scala version when cross-building.", CSetting)
|
||||
val classpathOptions = SettingKey[ClasspathOptions]("classpath-options", "Configures handling of Scala classpaths.", DSetting)
|
||||
val definedSbtPlugins = TaskKey[Set[String]]("defined-sbt-plugins", "The set of names of Plugin implementations defined by this project.", CTask)
|
||||
val discoveredSbtPlugins = TaskKey[PluginDiscovery.DiscoveredNames]("discovered-sbt-plugins", "The names of sbt plugin-related modules (modules that extend Build, Plugin, AutoImport, AutoPlugin) defined by this project.", CTask)
|
||||
val sbtPlugin = SettingKey[Boolean]("sbt-plugin", "If true, enables adding sbt as a dependency and auto-generation of the plugin descriptor file.", BMinusSetting)
|
||||
val printWarnings = TaskKey[Unit]("print-warnings", "Shows warnings from compilation, including ones that weren't printed initially.", BPlusTask)
|
||||
val fileInputOptions = SettingKey[Seq[String]]("file-input-options", "Options that take file input, which may invalidate the cache.", CSetting)
|
||||
|
|
@ -348,7 +349,7 @@ object Keys
|
|||
|
||||
// Experimental in sbt 0.13.2 to enable grabing semantic compile failures.
|
||||
private[sbt] val compilerReporter = TaskKey[Option[xsbti.Reporter]]("compilerReporter", "Experimental hook to listen (or send) compilation failure messages.", DTask)
|
||||
|
||||
|
||||
val triggeredBy = Def.triggeredBy
|
||||
val runBefore = Def.runBefore
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ package sbt
|
|||
import java.io.File
|
||||
import java.net.{URI,URL}
|
||||
import compiler.{Eval,EvalImports}
|
||||
import xsbt.api.{Discovered,Discovery}
|
||||
import xsbti.compile.CompileOrder
|
||||
import classpath.ClasspathUtilities
|
||||
import scala.annotation.tailrec
|
||||
|
|
@ -18,7 +17,6 @@ package sbt
|
|||
import Keys.{appConfiguration, baseDirectory, configuration, fullResolvers, fullClasspath, pluginData, streams, thisProject, thisProjectRef, update}
|
||||
import Keys.{exportedProducts, loadedBuild, onLoadMessage, resolvedScoped, sbtPlugin, scalacOptions, taskDefinitionKey}
|
||||
import tools.nsc.reporters.ConsoleReporter
|
||||
import Build.analyzed
|
||||
import Attributed.data
|
||||
import Scope.{GlobalScope, ThisScope}
|
||||
import Types.const
|
||||
|
|
@ -618,82 +616,23 @@ object Load
|
|||
ModuleUtilities.getObject(definition, loader).asInstanceOf[Build]
|
||||
|
||||
def loadPlugins(dir: File, data: PluginData, loader: ClassLoader): sbt.LoadedPlugins =
|
||||
new sbt.LoadedPlugins(dir, data, loader, autoDetect(data, loader))
|
||||
new sbt.LoadedPlugins(dir, data, loader, PluginDiscovery.discoverAll(data, loader))
|
||||
|
||||
private[this] def autoDetect(data: PluginData, loader: ClassLoader): DetectedPlugins =
|
||||
{
|
||||
// TODO: binary detection for builds, autoImports, autoPlugins
|
||||
import AutoBinaryResource._
|
||||
val plugins = detectModules[Plugin](data, loader, Plugins)
|
||||
val builds = detectModules[Build](data, loader, Builds)
|
||||
val autoImports = detectModules[AutoImport](data, loader, AutoImports)
|
||||
val autoPlugins = detectModules[AutoPlugin](data, loader, AutoPlugins)
|
||||
new DetectedPlugins(plugins, autoImports, autoPlugins, builds)
|
||||
}
|
||||
private[this] def detectModules[T](data: PluginData, loader: ClassLoader, resourceName: String)(implicit mf: reflect.ClassManifest[T]): DetectedModules[T] =
|
||||
{
|
||||
val classpath = data.classpath
|
||||
val namesAndValues = if(classpath.isEmpty) Nil else {
|
||||
val names = discoverModuleNames(classpath, loader, resourceName, mf.erasure.getName)
|
||||
loadModules[T](data, names, loader)
|
||||
}
|
||||
new DetectedModules(namesAndValues)
|
||||
}
|
||||
|
||||
private[this] def loadModules[T: ClassManifest](data: PluginData, names: Seq[String], loader: ClassLoader): Seq[(String,T)] =
|
||||
try ModuleUtilities.getCheckedObjects[T](names, loader)
|
||||
catch {
|
||||
case e: ExceptionInInitializerError =>
|
||||
val cause = e.getCause
|
||||
if(cause eq null) throw e else throw cause
|
||||
case e: LinkageError => incompatiblePlugins(data, e)
|
||||
}
|
||||
|
||||
private[this] def incompatiblePlugins(data: PluginData, t: LinkageError): Nothing =
|
||||
{
|
||||
val evicted = data.report.toList.flatMap(_.configurations.flatMap(_.evicted))
|
||||
val evictedModules = evicted map { id => (id.organization, id.name) } distinct ;
|
||||
val evictedStrings = evictedModules map { case (o,n) => o + ":" + n }
|
||||
val msgBase = "Binary incompatibility in plugins detected."
|
||||
val msgExtra = if(evictedStrings.isEmpty) "" else "\nNote that conflicts were resolved for some dependencies:\n\t" + evictedStrings.mkString("\n\t")
|
||||
throw new IncompatiblePluginsException(msgBase + msgExtra, t)
|
||||
}
|
||||
|
||||
def discoverModuleNames(classpath: Seq[Attributed[File]], loader: ClassLoader, resourceName: String, moduleTypes: String*): Seq[String] =
|
||||
(
|
||||
binaryPlugins(data(classpath), loader, resourceName) ++
|
||||
(analyzed(classpath) flatMap (a => discover(a, moduleTypes : _*)))
|
||||
).distinct
|
||||
|
||||
@deprecated("Replaced by the more general discoverModuleNames and will be made private.", "0.13.2")
|
||||
@deprecated("Replaced by the more general PluginDiscovery.binarySourceModuleNames and will be made private.", "0.13.2")
|
||||
def getPluginNames(classpath: Seq[Attributed[File]], loader: ClassLoader): Seq[String] =
|
||||
discoverModuleNames(classpath, loader, AutoBinaryResource.Plugins, classOf[Plugin].getName)
|
||||
PluginDiscovery.binarySourceModuleNames(classpath, loader, PluginDiscovery.Paths.Plugins, classOf[Plugin].getName)
|
||||
|
||||
@deprecated("Explicitly specify the resource name.", "0.13.2")
|
||||
@deprecated("Use PluginDiscovery.binaryModuleNames.", "0.13.2")
|
||||
def binaryPlugins(classpath: Seq[File], loader: ClassLoader): Seq[String] =
|
||||
binaryPlugins(classpath, loader, AutoBinaryResource.Plugins)
|
||||
PluginDiscovery.binaryModuleNames(classpath, loader, PluginDiscovery.Paths.Plugins)
|
||||
|
||||
/** Relative paths of resources that list top-level modules that are available.
|
||||
* Normally, the classes for those modules will be in the same classpath entry as the resource. */
|
||||
object AutoBinaryResource {
|
||||
final val AutoPlugins = "sbt/sbt.autoplugins"
|
||||
final val Plugins = "sbt/sbt.plugins"
|
||||
final val Builds = "sbt/sbt.builds"
|
||||
final val AutoImports = "sbt/sbt.autoimports"
|
||||
}
|
||||
def binaryPlugins(classpath: Seq[File], loader: ClassLoader, resourceName: String): Seq[String] =
|
||||
{
|
||||
import collection.JavaConversions._
|
||||
loader.getResources(resourceName).toSeq.filter(onClasspath(classpath)) flatMap { u =>
|
||||
IO.readLinesURL(u).map( _.trim).filter(!_.isEmpty)
|
||||
}
|
||||
}
|
||||
@deprecated("Use PluginDiscovery.onClasspath", "0.13.2")
|
||||
def onClasspath(classpath: Seq[File])(url: URL): Boolean =
|
||||
IO.urlAsFile(url) exists (classpath.contains _)
|
||||
PluginDiscovery.onClasspath(classpath)(url)
|
||||
|
||||
@deprecated("Use ModuleUtilities.getCheckedObjects[Plugin].", "0.13.2")
|
||||
def loadPlugins(loader: ClassLoader, pluginNames: Seq[String]): Seq[Plugin] =
|
||||
ModuleUtilities.getCheckedObjects[Plugin](loader, pluginNames)
|
||||
ModuleUtilities.getCheckedObjects[Plugin](pluginNames, loader).map(_._2)
|
||||
|
||||
@deprecated("Use ModuleUtilities.getCheckedObject[Plugin].", "0.13.2")
|
||||
def loadPlugin(pluginName: String, loader: ClassLoader): Plugin =
|
||||
|
|
@ -702,17 +641,12 @@ object Load
|
|||
@deprecated("No longer used.", "0.13.2")
|
||||
def findPlugins(analysis: inc.Analysis): Seq[String] = discover(analysis, "sbt.Plugin")
|
||||
|
||||
@deprecated("No longer used.", "0.13.2")
|
||||
def findDefinitions(analysis: inc.Analysis): Seq[String] = discover(analysis, "sbt.Build")
|
||||
|
||||
@deprecated("Use PluginDiscovery.sourceModuleNames", "0.13.2")
|
||||
def discover(analysis: inc.Analysis, subclasses: String*): Seq[String] =
|
||||
{
|
||||
val subclassSet = subclasses.toSet
|
||||
val ds = Discovery(subclassSet, Set.empty)(Tests.allDefs(analysis))
|
||||
ds.flatMap {
|
||||
case (definition, Discovered(subs,_,_,true)) =>
|
||||
if((subs & subclassSet).isEmpty) Nil else definition.name :: Nil
|
||||
case _ => Nil
|
||||
}
|
||||
}
|
||||
PluginDiscovery.sourceModuleNames(analysis, subclasses : _*)
|
||||
|
||||
def initialSession(structure: sbt.BuildStructure, rootEval: () => Eval, s: State): SessionSettings = {
|
||||
val session = s get Keys.sessionSettings
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import Attributed.data
|
||||
import Build.analyzed
|
||||
import xsbt.api.{Discovered,Discovery}
|
||||
|
||||
object PluginDiscovery
|
||||
{
|
||||
/** Relative paths of resources that list top-level modules that are available.
|
||||
* Normally, the classes for those modules will be in the same classpath entry as the resource. */
|
||||
object Paths
|
||||
{
|
||||
final val AutoPlugins = "sbt/sbt.autoplugins"
|
||||
final val Plugins = "sbt/sbt.plugins"
|
||||
final val Builds = "sbt/sbt.builds"
|
||||
final val AutoImports = "sbt/sbt.autoimports"
|
||||
}
|
||||
final class DiscoveredNames(val plugins: Seq[String], val autoImports: Seq[String], val autoPlugins: Seq[String], val builds: Seq[String])
|
||||
def emptyDiscoveredNames: DiscoveredNames = new DiscoveredNames(Nil, Nil, Nil, Nil)
|
||||
|
||||
def discoverAll(data: PluginData, loader: ClassLoader): DetectedPlugins =
|
||||
{
|
||||
def discover[T](resource: String)(implicit mf: reflect.ClassManifest[T]) =
|
||||
binarySourceModules[T](data, loader, resource)
|
||||
import Paths._
|
||||
new DetectedPlugins(discover[Plugin](Plugins), discover[AutoImport](AutoImports), discover[AutoPlugin](AutoPlugins), discover[Build](Builds))
|
||||
}
|
||||
def discoverSourceAll(analysis: inc.Analysis): DiscoveredNames =
|
||||
{
|
||||
def discover[T](implicit mf: reflect.ClassManifest[T]): Seq[String] =
|
||||
sourceModuleNames(analysis, mf.erasure.getName)
|
||||
new DiscoveredNames(discover[Plugin], discover[AutoImport], discover[AutoPlugin], discover[Build])
|
||||
}
|
||||
|
||||
// TODO: for 0.14.0, consider consolidating into a single file, which would make the classpath search 4x faster
|
||||
def writeDescriptors(names: DiscoveredNames, dir: File): Seq[File] =
|
||||
{
|
||||
import Paths._
|
||||
val files =
|
||||
writeDescriptor(names.plugins, dir, Plugins) ::
|
||||
writeDescriptor(names.autoPlugins, dir, AutoPlugins) ::
|
||||
writeDescriptor(names.builds, dir, Builds) ::
|
||||
writeDescriptor(names.autoImports, dir, AutoImports) ::
|
||||
Nil
|
||||
files.flatMap(_.toList)
|
||||
}
|
||||
|
||||
def writeDescriptor(names: Seq[String], dir: File, path: String): Option[File] =
|
||||
{
|
||||
val descriptor: File = new File(dir, path)
|
||||
if(names.isEmpty)
|
||||
{
|
||||
IO.delete(descriptor)
|
||||
None
|
||||
}
|
||||
else
|
||||
{
|
||||
IO.writeLines(descriptor, names.distinct.sorted)
|
||||
Some(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def binarySourceModuleNames(classpath: Seq[Attributed[File]], loader: ClassLoader, resourceName: String, subclasses: String*): Seq[String] =
|
||||
(
|
||||
binaryModuleNames(data(classpath), loader, resourceName) ++
|
||||
(analyzed(classpath) flatMap ( a => sourceModuleNames(a, subclasses : _*) ))
|
||||
).distinct
|
||||
|
||||
def sourceModuleNames(analysis: inc.Analysis, subclasses: String*): Seq[String] =
|
||||
{
|
||||
val subclassSet = subclasses.toSet
|
||||
val ds = Discovery(subclassSet, Set.empty)(Tests.allDefs(analysis))
|
||||
ds.flatMap {
|
||||
case (definition, Discovered(subs,_,_,true)) =>
|
||||
if((subs & subclassSet).isEmpty) Nil else definition.name :: Nil
|
||||
case _ => Nil
|
||||
}
|
||||
}
|
||||
|
||||
def binaryModuleNames(classpath: Seq[File], loader: ClassLoader, resourceName: String): Seq[String] =
|
||||
{
|
||||
import collection.JavaConversions._
|
||||
loader.getResources(resourceName).toSeq.filter(onClasspath(classpath)) flatMap { u =>
|
||||
IO.readLinesURL(u).map( _.trim).filter(!_.isEmpty)
|
||||
}
|
||||
}
|
||||
def onClasspath(classpath: Seq[File])(url: URL): Boolean =
|
||||
IO.urlAsFile(url) exists (classpath.contains _)
|
||||
|
||||
private[sbt] def binarySourceModules[T](data: PluginData, loader: ClassLoader, resourceName: String)(implicit mf: reflect.ClassManifest[T]): DetectedModules[T] =
|
||||
{
|
||||
val classpath = data.classpath
|
||||
val namesAndValues = if(classpath.isEmpty) Nil else {
|
||||
val names = binarySourceModuleNames(classpath, loader, resourceName, mf.erasure.getName)
|
||||
loadModules[T](data, names, loader)
|
||||
}
|
||||
new DetectedModules(namesAndValues)
|
||||
}
|
||||
|
||||
private[this] def loadModules[T: ClassManifest](data: PluginData, names: Seq[String], loader: ClassLoader): Seq[(String,T)] =
|
||||
try ModuleUtilities.getCheckedObjects[T](names, loader)
|
||||
catch {
|
||||
case e: ExceptionInInitializerError =>
|
||||
val cause = e.getCause
|
||||
if(cause eq null) throw e else throw cause
|
||||
case e: LinkageError => incompatiblePlugins(data, e)
|
||||
}
|
||||
|
||||
private[this] def incompatiblePlugins(data: PluginData, t: LinkageError): Nothing =
|
||||
{
|
||||
val evicted = data.report.toList.flatMap(_.configurations.flatMap(_.evicted))
|
||||
val evictedModules = evicted map { id => (id.organization, id.name) } distinct ;
|
||||
val evictedStrings = evictedModules map { case (o,n) => o + ":" + n }
|
||||
val msgBase = "Binary incompatibility in plugins detected."
|
||||
val msgExtra = if(evictedStrings.isEmpty) "" else "\nNote that conflicts were resolved for some dependencies:\n\t" + evictedStrings.mkString("\n\t")
|
||||
throw new IncompatiblePluginsException(msgBase + msgExtra, t)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue