mirror of https://github.com/sbt/sbt.git
Add extraProjects adn derivedProjects. Fixes #2532
This adds support to generate synthetic subprojects from an auto plugin. In addition, a method called `projectOrigin` is added to distinguish Organic, BuildExtra, ProjectExtra, and GenericRoot. Forward-port of #2717 and #2738
This commit is contained in:
parent
f2006e22cf
commit
0321f0cd48
|
|
@ -93,6 +93,12 @@ abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
|
|||
|
||||
// TODO?: def commands: Seq[Command]
|
||||
|
||||
/** The [[Project]]s to add to the current build. */
|
||||
def extraProjects: Seq[Project] = Nil
|
||||
|
||||
/** The [[Project]]s to add to the current build based on an existing project. */
|
||||
def derivedProjects(proj: ProjectDefinition[_]): Seq[Project] = Nil
|
||||
|
||||
private[sbt] def unary_! : Exclude = Exclude(this)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,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]
|
||||
|
||||
|
|
@ -107,7 +110,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] {
|
|||
dependenciesEval = dependenciesEval,
|
||||
delegatesEval = delegatesEval,
|
||||
settingsEval = settingsEval,
|
||||
configurations, auto, plugins, autoPlugins)
|
||||
configurations, auto, plugins, autoPlugins, projectOrigin)
|
||||
|
||||
def resolve(resolveRef: ProjectReference => ProjectRef): ResolvedProject =
|
||||
{
|
||||
|
|
@ -119,7 +122,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] {
|
|||
dependenciesEval = dependenciesEval map resolveDeps,
|
||||
delegatesEval = delegatesEval map resolveRefs,
|
||||
settingsEval,
|
||||
configurations, auto, plugins, autoPlugins)
|
||||
configurations, auto, plugins, autoPlugins, projectOrigin)
|
||||
}
|
||||
def resolveBuild(resolveRef: ProjectReference => ProjectReference): Project =
|
||||
{
|
||||
|
|
@ -131,7 +134,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] {
|
|||
dependenciesEval = dependenciesEval map resolveDeps,
|
||||
delegatesEval = delegatesEval map resolveRefs,
|
||||
settingsEval,
|
||||
configurations, auto, plugins, autoPlugins)
|
||||
configurations, auto, plugins, autoPlugins, projectOrigin)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -229,13 +232,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, aggregateEval = aggregateEval, dependenciesEval = dependenciesEval, delegatesEval = delegatesEval, settingsEval, configurations, auto, ns, autoPlugins)
|
||||
unresolved(id, base, aggregateEval = aggregateEval, dependenciesEval = dependenciesEval, delegatesEval = delegatesEval, settingsEval, 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, aggregateEval = aggregateEval, dependenciesEval = dependenciesEval, delegatesEval = delegatesEval, settingsEval, configurations, auto, plugins, autos)
|
||||
unresolved(id, base, aggregateEval = aggregateEval, dependenciesEval = dependenciesEval, delegatesEval = delegatesEval, settingsEval, 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, aggregateEval = aggregateEval, dependenciesEval = dependenciesEval, delegatesEval = delegatesEval, settingsEval, configurations, auto, plugins, autoPlugins, origin)
|
||||
}
|
||||
}
|
||||
sealed trait ResolvedProject extends ProjectDefinition[ProjectRef] {
|
||||
|
|
@ -247,6 +256,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 ExtraProject extends ProjectOrigin
|
||||
case object DerivedProject extends ProjectOrigin
|
||||
case object GenericRoot extends ProjectOrigin
|
||||
}
|
||||
|
||||
object Project extends ProjectExtra {
|
||||
|
||||
private abstract class ProjectDef[PR <: ProjectReference](
|
||||
|
|
@ -259,7 +280,8 @@ object Project extends ProjectExtra {
|
|||
val configurations: Seq[Configuration],
|
||||
val auto: AddSettings,
|
||||
val plugins: Plugins,
|
||||
val autoPlugins: Seq[AutoPlugin]) extends ProjectDefinition[PR] {
|
||||
val autoPlugins: Seq[AutoPlugin],
|
||||
val projectOrigin: ProjectOrigin) extends ProjectDefinition[PR] {
|
||||
def aggregate: Seq[PR] = aggregateEval.get
|
||||
def dependencies: Seq[ClasspathDep[PR]] = dependenciesEval.get
|
||||
def delegates: Seq[PR] = delegatesEval.get
|
||||
|
|
@ -278,9 +300,9 @@ object Project extends ProjectExtra {
|
|||
}
|
||||
|
||||
def apply(id: String, base: File): Project =
|
||||
unresolved(id, base, evalNil, evalNil, evalNil, evalNil, Nil, AddSettings.allDefaults, Plugins.empty, Nil)
|
||||
unresolved(id, base, evalNil, evalNil, evalNil, evalNil, Nil, AddSettings.allDefaults, Plugins.empty, Nil, ProjectOrigin.Organic)
|
||||
|
||||
// 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,
|
||||
|
|
@ -303,7 +325,7 @@ object Project extends ProjectExtra {
|
|||
private[sbt] def mkGeneratedRoot(id: String, base: File, aggregate: Eval[Seq[ProjectReference]]): Project =
|
||||
{
|
||||
validProjectID(id).foreach(errMsg => sys.error("Invalid project ID: " + errMsg))
|
||||
new ProjectDef[ProjectReference](id, base, aggregate, evalNil, evalNil, evalNil, Nil, AddSettings.allDefaults, Plugins.empty, Nil) with Project with GeneratedRootProject
|
||||
new ProjectDef[ProjectReference](id, base, aggregate, evalNil, evalNil, evalNil, 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.*/
|
||||
|
|
@ -330,15 +352,15 @@ object Project extends ProjectExtra {
|
|||
|
||||
private def resolved(id: String, base: File, aggregateEval: Eval[Seq[ProjectRef]], dependenciesEval: Eval[Seq[ClasspathDep[ProjectRef]]],
|
||||
delegatesEval: Eval[Seq[ProjectRef]], settingsEval: Eval[Seq[Def.Setting[_]]], configurations: Seq[Configuration], auto: AddSettings,
|
||||
plugins: Plugins, autoPlugins: Seq[AutoPlugin]): ResolvedProject =
|
||||
new ProjectDef[ProjectRef](id, base, aggregateEval, dependenciesEval, delegatesEval, settingsEval, configurations, auto, plugins, autoPlugins) with ResolvedProject
|
||||
plugins: Plugins, autoPlugins: Seq[AutoPlugin], origin: ProjectOrigin): ResolvedProject =
|
||||
new ProjectDef[ProjectRef](id, base, aggregateEval, dependenciesEval, delegatesEval, settingsEval, configurations, auto, plugins, autoPlugins, origin) with ResolvedProject
|
||||
|
||||
private def unresolved(id: String, base: File, aggregateEval: Eval[Seq[ProjectReference]], dependenciesEval: Eval[Seq[ClasspathDep[ProjectReference]]],
|
||||
delegatesEval: Eval[Seq[ProjectReference]], settingsEval: Eval[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, aggregateEval, dependenciesEval, delegatesEval, settingsEval, configurations, auto, plugins, autoPlugins) with Project
|
||||
new ProjectDef[ProjectReference](id, base, aggregateEval, dependenciesEval, delegatesEval, settingsEval, configurations, auto, plugins, autoPlugins, origin) with Project
|
||||
}
|
||||
|
||||
final class Constructor(p: ProjectReference) {
|
||||
|
|
|
|||
|
|
@ -436,10 +436,13 @@ private[sbt] object Load {
|
|||
|
||||
val plugs = plugins(defDir, s, config.copy(pluginManagement = config.pluginManagement.forPlugin))
|
||||
val defsScala = plugs.detected.builds.values
|
||||
val buildLevelExtraProjects = plugs.detected.autoPlugins flatMap { d =>
|
||||
d.value.extraProjects map {_.setProjectOrigin(ProjectOrigin.ExtraProject)}
|
||||
}
|
||||
|
||||
// NOTE - because we create an eval here, we need a clean-eval later for this URI.
|
||||
lazy val eval = 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)
|
||||
|
||||
|
|
@ -549,10 +552,24 @@ private[sbt] 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 =
|
||||
configFiles.flatMap(_.manipulations).foldLeft(p) { (prev, t) =>
|
||||
t(prev)
|
||||
}
|
||||
val autoPlugins: Seq[AutoPlugin] =
|
||||
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 { _.derivedProjects(p2) map {_.setProjectOrigin(ProjectOrigin.DerivedProject)} }
|
||||
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.
|
||||
|
|
@ -565,9 +582,10 @@ private[sbt] 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 {
|
||||
|
|
@ -580,8 +598,8 @@ private[sbt] 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 = finalizeProject(root, files)
|
||||
loadTransitive(discovered, buildBase, plugins, eval, injectSettings, finalRoot +: acc, memoSettings, log, false, buildUri, context, generated ++ generatedConfigClassFiles)
|
||||
val (finalRoot, projectLevelExtra) = 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(",")}")
|
||||
|
|
@ -593,7 +611,7 @@ private[sbt] object Load {
|
|||
val defaultID = autoID(buildBase, context, existingIds)
|
||||
val root0 = if (discovered.isEmpty || java.lang.Boolean.getBoolean("sbt.root.ivyplugin")) BuildDef.defaultAggregatedProject(defaultID, buildBase, refs)
|
||||
else BuildDef.generatedRootWithoutIvyPlugin(defaultID, buildBase, refs)
|
||||
val root = finalizeProject(root0, files)
|
||||
val (root, _) = 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)
|
||||
|
|
@ -625,13 +643,11 @@ private[sbt] 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
|
||||
|
|
@ -639,32 +655,23 @@ private[sbt] 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: LoadedPlugins,
|
||||
globalUserSettings: InjectSettings,
|
||||
memoSettings: mutable.Map[File, LoadedSbtFile],
|
||||
log: Logger): Project = {
|
||||
import AddSettings._
|
||||
// 1. Apply all the project manipulations from .sbt files in order
|
||||
val transformedProject =
|
||||
configFiles.flatMap(_.manipulations).foldLeft(rawProject) { (prev, t) =>
|
||||
t(prev)
|
||||
}
|
||||
// 2. Discover all the autoplugins and contributed configurations.
|
||||
val autoPlugins =
|
||||
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)
|
||||
// Filter the AutoPlugin settings we included based on which ones are
|
||||
// intended in the AddSettings.AutoPlugins filter.
|
||||
def autoPluginSettings(f: AutoPlugins) =
|
||||
autoPlugins.filter(f.include).flatMap(_.projectSettings)
|
||||
projectPlugins.filter(f.include).flatMap(_.projectSettings)
|
||||
// Grab all the settigns we already loaded from sbt files
|
||||
def settings(files: Seq[File]): Seq[Setting[_]] =
|
||||
for {
|
||||
|
|
@ -674,17 +681,17 @@ private[sbt] object Load {
|
|||
} yield setting
|
||||
// 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.projectLoaded(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: AutoPlugins => autoPluginSettings(p)
|
||||
case q: Sequence => (Seq.empty[Setting[_]] /: q.sequence) { (b, add) => b ++ expandSettings(add) }
|
||||
}
|
||||
expandSettings(transformedProject.auto)
|
||||
expandSettings(p.auto)
|
||||
}
|
||||
// Finally, a project we can use in buildStructure.
|
||||
transformedProject.copy(settingsEval = Ev.later(allSettings)).setAutoPlugins(autoPlugins).prefixConfigs(autoConfigs: _*)
|
||||
p.copy(settingsEval = Ev.later(allSettings)).setAutoPlugins(projectPlugins).prefixConfigs(autoConfigs: _*)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import sbt._, syntax._, 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"
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import sbt._, syntax._, Keys._
|
||||
|
||||
object ExtraProjectPluginExample extends AutoPlugin {
|
||||
override def extraProjects: Seq[Project] =
|
||||
List("foo", "bar", "baz") map generateProject
|
||||
|
||||
def generateProject(id: String): Project =
|
||||
Project(id, file(id)).
|
||||
settings(
|
||||
name := id
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import sbt._, syntax._, Keys._
|
||||
|
||||
object ExtraProjectPluginExample2 extends AutoPlugin {
|
||||
// Enable this plugin by default
|
||||
override def requires: Plugins = sbt.plugins.CorePlugin
|
||||
override def trigger = allRequirements
|
||||
|
||||
override def derivedProjects(proj: ProjectDefinition[_]): Seq[Project] =
|
||||
// Make sure to exclude project extras to avoid recursive generation
|
||||
if (proj.projectOrigin != ProjectOrigin.DerivedProject) {
|
||||
val id = proj.id + "1"
|
||||
Seq(
|
||||
Project(id, file(id)).
|
||||
enablePlugins(DatabasePlugin)
|
||||
)
|
||||
}
|
||||
else Nil
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
> bar/compile
|
||||
|
||||
> foo1/compile
|
||||
|
||||
> foo1/databaseName
|
||||
Loading…
Reference in New Issue