Merge pull request #2717 from eed3si9n/wip/plugin

Add buildExtras and projectExtras
This commit is contained in:
eugene yokota 2016-08-27 15:03:27 -04:00 committed by GitHub
commit d0ce0cef49
8 changed files with 190 additions and 56 deletions

View File

@ -467,10 +467,13 @@ object Load {
val defsScala = timed("Load.loadUnit: defsScala", log) {
plugs.detected.builds.values
}
val buildLevelExtraProjects = plugs.detected.autoPlugins flatMap { d =>
d.value.buildExtras map {_.setProjectOrigin(ProjectOrigin.BuildExtra)}
}
// NOTE - because we create an eval here, we need a clean-eval later for this URI.
lazy val eval = timed("Load.loadUnit: mkEval", log) { mkEval(plugs.classpath, defDir, plugs.pluginData.scalacOptions) }
val initialProjects = defsScala.flatMap(b => projectsFromBuild(b, normBase))
val initialProjects = defsScala.flatMap(b => projectsFromBuild(b, normBase)) ++ buildLevelExtraProjects
val hasRootAlreadyDefined = defsScala.exists(_.rootProject.isDefined)
@ -580,10 +583,28 @@ object Load {
// load all relevant configuration files (.sbt, as .scala already exists at this point)
def discover(auto: AddSettings, base: File): DiscoveredProjects =
discoverProjects(auto, base, plugins, eval, memoSettings)
// Step two, Finalize a project with all its settings/configuration.
def finalizeProject(p: Project, configFiles: Seq[File]): Project = {
val loadedFiles = configFiles flatMap { f => memoSettings.get(f) }
resolveProject(p, loadedFiles, plugins, injectSettings, memoSettings, log)
// Step two:
// a. Apply all the project manipulations from .sbt files in order
// b. Deduce the auto plugins for the project
// c. Finalize a project with all its settings/configuration.
def finalizeProject(p: Project, files: Seq[File], expand: Boolean): (Project, Seq[Project]) = {
val configFiles = files flatMap { f => memoSettings.get(f) }
val p1: Project =
timed(s"Load.loadTransitive(${p.id}): transformedProject", log) {
configFiles.flatMap(_.manipulations).foldLeft(p) { (prev, t) =>
t(prev)
}
}
val autoPlugins: Seq[AutoPlugin] =
timed(s"Load.loadTransitive(${p.id}): autoPlugins", log) {
try plugins.detected.deducePluginsFromProject(p1, log)
catch { case e: AutoPluginException => throw translateAutoPluginException(e, p) }
}
val p2 = this.resolveProject(p1, autoPlugins, plugins, injectSettings, memoSettings, log)
val projectLevelExtra =
if (expand) autoPlugins flatMap { _.projectExtras(p2) map {_.setProjectOrigin(ProjectOrigin.ProjectExtra)} }
else Nil
(p2, projectLevelExtra)
}
// Discover any new project definition for the base directory of this project, and load all settings.
// Also return any newly discovered project instances.
@ -596,9 +617,10 @@ object Load {
(root, rest, files, generated)
case DiscoveredProjects(None, rest, files, generated) => (p, rest, files, generated)
}
val finalRoot = finalizeProject(root, files)
(finalRoot, discovered, generated)
val (finalRoot, projectLevelExtra) = finalizeProject(root, files, true)
(finalRoot, discovered ++ projectLevelExtra, generated)
}
// Load all config files AND finalize the project at the root directory, if it exists.
// Continue loading if we find any more.
newProjects match {
@ -611,8 +633,10 @@ object Load {
discover(AddSettings.defaultSbtFiles, buildBase) match {
case DiscoveredProjects(Some(root), discovered, files, generated) =>
log.debug(s"[Loading] Found root project ${root.id} w/ remaining ${discovered.map(_.id).mkString(",")}")
val finalRoot = timed(s"Load.loadTransitive: finalizeProject($root)", log) { finalizeProject(root, files) }
loadTransitive(discovered, buildBase, plugins, eval, injectSettings, finalRoot +: acc, memoSettings, log, false, buildUri, context, generated ++ generatedConfigClassFiles)
val (finalRoot, projectLevelExtra) = timed(s"Load.loadTransitive: finalizeProject($root)", log) {
finalizeProject(root, files, true)
}
loadTransitive(discovered ++ projectLevelExtra, buildBase, plugins, eval, injectSettings, finalRoot +: acc, memoSettings, log, false, buildUri, context, generated ++ generatedConfigClassFiles)
// Here we need to create a root project...
case DiscoveredProjects(None, discovered, files, generated) =>
log.debug(s"[Loading] Found non-root projects ${discovered.map(_.id).mkString(",")}")
@ -624,7 +648,9 @@ object Load {
val defaultID = autoID(buildBase, context, existingIds)
val root0 = if (discovered.isEmpty || java.lang.Boolean.getBoolean("sbt.root.ivyplugin")) Build.defaultAggregatedProject(defaultID, buildBase, refs)
else Build.generatedRootWithoutIvyPlugin(defaultID, buildBase, refs)
val root = timed(s"Load.loadTransitive: finalizeProject2($root0)", log) { finalizeProject(root0, files) }
val (root, _) = timed(s"Load.loadTransitive: finalizeProject2($root0)", log) {
finalizeProject(root0, files, false)
}
val result = root +: (acc ++ otherProjects.projects)
log.debug(s"[Loading] Done in ${buildBase}, returning: ${result.map(_.id).mkString("(", ", ", ")")}")
LoadedProjects(result, generated ++ otherGenerated ++ generatedConfigClassFiles)
@ -656,13 +682,11 @@ object Load {
/**
* This method attempts to resolve/apply all configuration loaded for a project. It is responsible for the following:
*
* 1. Apply any manipulations defined in .sbt files.
* 2. Detecting which autoPlugins are enabled for the project.
* 3. Ordering all Setting[_]s for the project
* Ordering all Setting[_]s for the project
*
*
* @param rawProject The original project, with nothing manipulated since it was evaluated/discovered.
* @param configFiles All configuration files loaded for this project. Used to discover project manipulations
* @param transformedProject The project with manipulation.
* @param projectPlugins The deduced list of plugins for the given project.
* @param loadedPlugins The project definition (and classloader) of the build.
* @param globalUserSettings All the settings contributed from the ~/.sbt/<version> directory
* @param memoSettings A recording of all loaded files (our files should reside in there). We should need not load any
@ -670,48 +694,35 @@ object Load {
* @param log A logger to report auto-plugin issues to.
*/
private[this] def resolveProject(
rawProject: Project,
configFiles: Seq[LoadedSbtFile],
p: Project,
projectPlugins: Seq[AutoPlugin],
loadedPlugins: sbt.LoadedPlugins,
globalUserSettings: InjectSettings,
memoSettings: mutable.Map[File, LoadedSbtFile],
log: Logger): Project =
timed(s"Load.resolveProject(${rawProject.id})", log) {
timed(s"Load.resolveProject(${p.id})", log) {
import AddSettings._
// 1. Apply all the project manipulations from .sbt files in order
val transformedProject =
timed(s"Load.resolveProject(${rawProject.id}): transformedProject", log) {
configFiles.flatMap(_.manipulations).foldLeft(rawProject) { (prev, t) =>
t(prev)
}
}
// 2. Discover all the autoplugins and contributed configurations.
val autoPlugins =
timed(s"Load.resolveProject(${rawProject.id}): autoPlugins", log) {
try loadedPlugins.detected.deducePluginsFromProject(transformedProject, log)
catch { case e: AutoPluginException => throw translateAutoPluginException(e, transformedProject) }
}
val autoConfigs = autoPlugins.flatMap(_.projectConfigurations)
val autoConfigs = projectPlugins.flatMap(_.projectConfigurations)
// 3. Use AddSettings instance to order all Setting[_]s appropriately
val allSettings = {
// TODO - This mechanism of applying settings could be off... It's in two places now...
lazy val defaultSbtFiles = configurationSources(transformedProject.base)
lazy val defaultSbtFiles = configurationSources(p.base)
// Grabs the plugin settings for old-style sbt plugins.
def pluginSettings(f: Plugins) =
timed(s"Load.resolveProject(${rawProject.id}): expandSettings(...): pluginSettings($f)", log) {
timed(s"Load.resolveProject(${p.id}): expandSettings(...): pluginSettings($f)", log) {
val included = loadedPlugins.detected.plugins.values.filter(f.include) // don't apply the filter to AutoPlugins, only Plugins
included.flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings)
}
// Filter the AutoPlugin settings we included based on which ones are
// intended in the AddSettings.AutoPlugins filter.
def autoPluginSettings(f: AutoPlugins) =
timed(s"Load.resolveProject(${rawProject.id}): expandSettings(...): autoPluginSettings($f)", log) {
autoPlugins.filter(f.include).flatMap(_.projectSettings)
timed(s"Load.resolveProject(${p.id}): expandSettings(...): autoPluginSettings($f)", log) {
projectPlugins.filter(f.include).flatMap(_.projectSettings)
}
// Grab all the settigns we already loaded from sbt files
def settings(files: Seq[File]): Seq[Setting[_]] =
timed(s"Load.resolveProject(${rawProject.id}): expandSettings(...): settings($files)", log) {
timed(s"Load.resolveProject(${p.id}): expandSettings(...): settings($files)", log) {
for {
file <- files
config <- (memoSettings get file).toSeq
@ -720,20 +731,20 @@ object Load {
}
// Expand the AddSettings instance into a real Seq[Setting[_]] we'll use on the project
def expandSettings(auto: AddSettings): Seq[Setting[_]] = auto match {
case BuildScalaFiles => rawProject.settings
case BuildScalaFiles => p.settings
case User => globalUserSettings.cachedProjectLoaded(loadedPlugins.loader)
case sf: SbtFiles => settings(sf.files.map(f => IO.resolve(rawProject.base, f)))
case sf: SbtFiles => settings(sf.files.map(f => IO.resolve(p.base, f)))
case sf: DefaultSbtFiles => settings(defaultSbtFiles.filter(sf.include))
case p: Plugins => pluginSettings(p)
case p: AutoPlugins => autoPluginSettings(p)
case q: Sequence => (Seq.empty[Setting[_]] /: q.sequence) { (b, add) => b ++ expandSettings(add) }
}
timed(s"Load.resolveProject(${rawProject.id}): expandSettings(...)", log) {
expandSettings(transformedProject.auto)
timed(s"Load.resolveProject(${p.id}): expandSettings(...)", log) {
expandSettings(p.auto)
}
}
// Finally, a project we can use in buildStructure.
transformedProject.copy(settings = allSettings).setAutoPlugins(autoPlugins).prefixConfigs(autoConfigs: _*)
p.copy(settings = allSettings).setAutoPlugins(projectPlugins).prefixConfigs(autoConfigs: _*)
}
/**

View File

@ -90,6 +90,12 @@ abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
// TODO?: def commands: Seq[Command]
/** The [[Project]]s to add to the current build. */
def buildExtras: Seq[Project] = Nil
/** The [[Project]]s to add to the current build based on an existing project. */
def projectExtras(proj: ProjectDefinition[_]): Seq[Project] = Nil
private[sbt] def unary_! : Exclude = Exclude(this)

View File

@ -64,6 +64,9 @@ sealed trait ProjectDefinition[PR <: ProjectReference] {
*/
def plugins: Plugins
/** Indicates whether the project was created organically, or was generated synthetically. */
def projectOrigin: ProjectOrigin
/** The [[AutoPlugin]]s enabled for this project. This value is only available on a loaded Project. */
private[sbt] def autoPlugins: Seq[AutoPlugin]
@ -88,7 +91,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] {
def copy(id: String = id, base: File = base, aggregate: => Seq[ProjectReference] = aggregate, dependencies: => Seq[ClasspathDep[ProjectReference]] = dependencies,
delegates: => Seq[ProjectReference] = delegates, settings: => Seq[Setting[_]] = settings, configurations: Seq[Configuration] = configurations,
auto: AddSettings = auto): Project =
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, plugins, autoPlugins)
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, plugins, autoPlugins, projectOrigin)
def resolve(resolveRef: ProjectReference => ProjectRef): ResolvedProject =
{
@ -96,7 +99,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] {
def resolveDeps(ds: Seq[ClasspathDep[ProjectReference]]) = ds map resolveDep
def resolveDep(d: ClasspathDep[ProjectReference]) = ResolvedClasspathDependency(resolveRef(d.project), d.configuration)
resolved(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates),
settings, configurations, auto, plugins, autoPlugins)
settings, configurations, auto, plugins, autoPlugins, projectOrigin)
}
def resolveBuild(resolveRef: ProjectReference => ProjectReference): Project =
{
@ -104,7 +107,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] {
def resolveDeps(ds: Seq[ClasspathDep[ProjectReference]]) = ds map resolveDep
def resolveDep(d: ClasspathDep[ProjectReference]) = ClasspathDependency(resolveRef(d.project), d.configuration)
unresolved(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates),
settings, configurations, auto, plugins, autoPlugins)
settings, configurations, auto, plugins, autoPlugins, projectOrigin)
}
/**
@ -174,13 +177,19 @@ sealed trait Project extends ProjectDefinition[ProjectReference] {
private[this] def setPlugins(ns: Plugins): Project = {
// TODO: for 0.14.0, use copy when it has the additional `plugins` parameter
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, ns, autoPlugins)
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, ns, autoPlugins, projectOrigin)
}
/** Definitively set the [[AutoPlugin]]s for this project. */
private[sbt] def setAutoPlugins(autos: Seq[AutoPlugin]): Project = {
// TODO: for 0.14.0, use copy when it has the additional `autoPlugins` parameter
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, plugins, autos)
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, plugins, autos, projectOrigin)
}
/** Definitively set the [[ProjectOrigin]] for this project. */
private[sbt] def setProjectOrigin(origin: ProjectOrigin): Project = {
// TODO: for 1.0.x, use withProjectOrigin.
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, plugins, autoPlugins, origin)
}
}
sealed trait ResolvedProject extends ProjectDefinition[ProjectRef] {
@ -192,6 +201,18 @@ sealed trait ClasspathDep[PR <: ProjectReference] { def project: PR; def configu
final case class ResolvedClasspathDependency(project: ProjectRef, configuration: Option[String]) extends ClasspathDep[ProjectRef]
final case class ClasspathDependency(project: ProjectReference, configuration: Option[String]) extends ClasspathDep[ProjectReference]
/**
* Indicate whether the project was created organically, synthesized by a plugin,
* or is a "generic root" project supplied by sbt when a project doesn't exist for `file(".")`.
*/
sealed trait ProjectOrigin
object ProjectOrigin {
case object Organic extends ProjectOrigin
case object BuildExtra extends ProjectOrigin
case object ProjectExtra extends ProjectOrigin
case object GenericRoot extends ProjectOrigin
}
object Project extends ProjectExtra {
@deprecated("Use Def.Setting", "0.13.0")
type Setting[T] = Def.Setting[T]
@ -219,7 +240,7 @@ object Project extends ProjectExtra {
private abstract class ProjectDef[PR <: ProjectReference](val id: String, val base: File, aggregate0: => Seq[PR], dependencies0: => Seq[ClasspathDep[PR]],
delegates0: => Seq[PR], settings0: => Seq[Def.Setting[_]], val configurations: Seq[Configuration], val auto: AddSettings,
val plugins: Plugins, val autoPlugins: Seq[AutoPlugin]) extends ProjectDefinition[PR] {
val plugins: Plugins, val autoPlugins: Seq[AutoPlugin], val projectOrigin: ProjectOrigin) extends ProjectDefinition[PR] {
lazy val aggregate = aggregate0
lazy val dependencies = dependencies0
lazy val delegates = delegates0
@ -228,18 +249,18 @@ object Project extends ProjectExtra {
Dag.topologicalSort(configurations)(_.extendsConfigs) // checks for cyclic references here instead of having to do it in Scope.delegates
}
// TODO: add parameter for plugins in 0.14.0
// TODO: add parameter for plugins and projectOrigin in 1.0
// TODO: Modify default settings to be the core settings, and automatically add the IvyModule + JvmPlugins.
def apply(id: String, base: File, aggregate: => Seq[ProjectReference] = Nil, dependencies: => Seq[ClasspathDep[ProjectReference]] = Nil,
delegates: => Seq[ProjectReference] = Nil, settings: => Seq[Def.Setting[_]] = Nil, configurations: Seq[Configuration] = Nil,
auto: AddSettings = AddSettings.allDefaults): Project =
unresolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Plugins.empty, Nil) // Note: JvmModule/IvyModule auto included...
unresolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Plugins.empty, Nil, ProjectOrigin.Organic) // Note: JvmModule/IvyModule auto included...
/** This is a variation of def apply that mixes in GeneratedRootProject. */
private[sbt] def mkGeneratedRoot(id: String, base: File, aggregate: => Seq[ProjectReference]): Project =
{
validProjectID(id).foreach(errMsg => sys.error("Invalid project ID: " + errMsg))
new ProjectDef[ProjectReference](id, base, aggregate, Nil, Nil, Nil, Nil, AddSettings.allDefaults, Plugins.empty, Nil) with Project with GeneratedRootProject
new ProjectDef[ProjectReference](id, base, aggregate, Nil, Nil, Nil, Nil, AddSettings.allDefaults, Plugins.empty, Nil, ProjectOrigin.GenericRoot) with Project with GeneratedRootProject
}
/** Returns None if `id` is a valid Project ID or Some containing the parser error message if it is not.*/
@ -267,19 +288,19 @@ object Project extends ProjectExtra {
@deprecated("Will be removed.", "0.13.2")
def resolved(id: String, base: File, aggregate: => Seq[ProjectRef], dependencies: => Seq[ResolvedClasspathDependency], delegates: => Seq[ProjectRef],
settings: Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings): ResolvedProject =
resolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Plugins.empty, Nil)
resolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Plugins.empty, Nil, ProjectOrigin.Organic)
private def resolved(id: String, base: File, aggregate: => Seq[ProjectRef], dependencies: => Seq[ClasspathDep[ProjectRef]],
delegates: => Seq[ProjectRef], settings: Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings,
plugins: Plugins, autoPlugins: Seq[AutoPlugin]): ResolvedProject =
new ProjectDef[ProjectRef](id, base, aggregate, dependencies, delegates, settings, configurations, auto, plugins, autoPlugins) with ResolvedProject
plugins: Plugins, autoPlugins: Seq[AutoPlugin], origin: ProjectOrigin): ResolvedProject =
new ProjectDef[ProjectRef](id, base, aggregate, dependencies, delegates, settings, configurations, auto, plugins, autoPlugins, origin) with ResolvedProject
private def unresolved(id: String, base: File, aggregate: => Seq[ProjectReference], dependencies: => Seq[ClasspathDep[ProjectReference]],
delegates: => Seq[ProjectReference], settings: => Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings,
plugins: Plugins, autoPlugins: Seq[AutoPlugin]): Project =
plugins: Plugins, autoPlugins: Seq[AutoPlugin], origin: ProjectOrigin): Project =
{
validProjectID(id).foreach(errMsg => sys.error("Invalid project ID: " + errMsg))
new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations, auto, plugins, autoPlugins) with Project
new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations, auto, plugins, autoPlugins, origin) with Project
}
@deprecated("Use Defaults.coreDefaultSettings instead, combined with AutoPlugins.", "0.13.2")

43
notes/0.13.13/extras.md Normal file
View File

@ -0,0 +1,43 @@
### Synthetic subprojects
sbt 0.13.13 adds support for `AutoPlugin`s to generate
synthetic subprojects. To generate subprojects, override `buildExtras`
method as follows:
import sbt._
import Keys._
object BuildExtrasPlugin extends AutoPlugin {
override def buildExtras: Seq[Project] =
List("foo", "bar", "baz") map generateProject
def generateProject(id: String): Project =
Project(id, file(id)).
settings(
name := id
)
}
In addition, subprojects may be derived from an existing subproject
by overriding `projectExtras`.
import sbt._
import Keys._
object ProjectExtrasPlugin extends AutoPlugin {
// Enable this plugin by default
override def requires: Plugins = sbt.plugins.CorePlugin
override def trigger = allRequirements
override def projectExtras(proj: ProjectDefinition[_]): Seq[Project] =
// Make sure to exclude project extras to avoid recursive generation
if (proj.projectOrigin != ProjectOrigin.ProjectExtra) {
val id = proj.id + "1"
Seq(
Project(id, file(id)).
enablePlugins(DatabasePlugin)
)
}
else Nil
}

View File

@ -0,0 +1,13 @@
import sbt._
import Keys._
object BuildExtrasPlugin extends AutoPlugin {
override def buildExtras: Seq[Project] =
List("foo", "bar", "baz") map generateProject
def generateProject(id: String): Project =
Project(id, file(id)).
settings(
name := id
)
}

View File

@ -0,0 +1,16 @@
import sbt._
import Keys._
object DatabasePlugin extends AutoPlugin {
override def requires: Plugins = sbt.plugins.JvmPlugin
override def trigger = noTrigger
object autoImport {
lazy val databaseName = settingKey[String]("name of the database")
}
import autoImport._
override def projectSettings: Seq[Setting[_]] =
Seq(
databaseName := "something"
)
}

View File

@ -0,0 +1,19 @@
import sbt._
import Keys._
object ProjectExtrasPlugin extends AutoPlugin {
// Enable this plugin by default
override def requires: Plugins = sbt.plugins.CorePlugin
override def trigger = allRequirements
override def projectExtras(proj: ProjectDefinition[_]): Seq[Project] =
// Make sure to exclude project extras to avoid recursive generation
if (proj.projectOrigin != ProjectOrigin.ProjectExtra) {
val id = proj.id + "1"
Seq(
Project(id, file(id)).
enablePlugins(DatabasePlugin)
)
}
else Nil
}

View File

@ -0,0 +1,5 @@
> bar/compile
> foo1/compile
> foo1/databaseName