From f99ba44703cfc1fe8ce6f61609129dafe684d47a Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 16 Nov 2012 09:56:49 -0500 Subject: [PATCH] Control over automatically added settings. Project.autoSettings accepts a sequence of AddSettings, instances of which are constructed from methods in AddSettings. The configurable settings are per-user settings (from ~/.sbt, for example), settings from .sbt files, and plugin settings (project-level only). The order in which these instances are provided to autoSettings determines the order in which they are appended to the settings explicitly provided in Project.settings. For .sbt files, defaultSbtFiles adds the settings from all .sbt files in the project's base directory as usual. AddSettings.sbtFiles accepts a sequence of Files that will be loaded according to the standard .sbt format. Relative Files are resolved against the project's base directory. Plugin settings may be included on a per-Plugin basis by using the plugins method and passing a Plugin => Boolean. The settings controlled here are only the automatic per-project settings. Per-build and global settings will always be included. --- main/AddSettings.scala | 38 +++++++++++++++++++ main/Load.scala | 31 +++++++++------ main/Project.scala | 28 ++++++++------ .../project/auto-settings/b/build.sbt | 1 + .../project/auto-settings/d/build.sbt | 1 + .../project/auto-settings/e/build.sbt | 1 + .../project/auto-settings/explicit/a.txt | 1 + .../project/auto-settings/global/user.sbt | 1 + .../project/auto-settings/project/P.scala | 31 +++++++++++++++ .../sbt-test/project/auto-settings/root.sbt | 1 + sbt/src/sbt-test/project/auto-settings/test | 11 ++++++ 11 files changed, 122 insertions(+), 23 deletions(-) create mode 100644 main/AddSettings.scala create mode 100644 sbt/src/sbt-test/project/auto-settings/b/build.sbt create mode 100644 sbt/src/sbt-test/project/auto-settings/d/build.sbt create mode 100644 sbt/src/sbt-test/project/auto-settings/e/build.sbt create mode 100644 sbt/src/sbt-test/project/auto-settings/explicit/a.txt create mode 100644 sbt/src/sbt-test/project/auto-settings/global/user.sbt create mode 100644 sbt/src/sbt-test/project/auto-settings/project/P.scala create mode 100644 sbt/src/sbt-test/project/auto-settings/root.sbt create mode 100644 sbt/src/sbt-test/project/auto-settings/test diff --git a/main/AddSettings.scala b/main/AddSettings.scala new file mode 100644 index 000000000..bf509255a --- /dev/null +++ b/main/AddSettings.scala @@ -0,0 +1,38 @@ +package sbt + + import Types.const + import java.io.File + +/** Represents how settings from various sources are automatically merged into a Project's settings. +* This only configures per-project settings and not global or per-build settings. */ +sealed abstract class AddSettings + +object AddSettings +{ + private[sbt] final class Sequence(val sequence: Seq[AddSettings]) extends AddSettings + private[sbt] final object User extends AddSettings + private[sbt] final class Plugins(val include: Plugin => Boolean) extends AddSettings + private[sbt] final class DefaultSbtFiles(val include: File => Boolean) extends AddSettings + private[sbt] final class SbtFiles(val files: Seq[File]) extends AddSettings + + /** Adds all settings from a plugin to a project. */ + val allPlugins: AddSettings = plugins(const(true)) + + /** Allows the plugins whose names match the `names` filter to automatically add settings to a project. */ + def plugins(include: Plugin => Boolean): AddSettings = new Plugins(include) + + /** Includes user settings in the project. */ + val userSettings: AddSettings = User + + /** Includes the settings from all .sbt files in the project's base directory. */ + val defaultSbtFiles: AddSettings = new DefaultSbtFiles(const(true)) + + /** Includes the settings from the .sbt files given by `files`. */ + def sbtFiles(files: File*): AddSettings = new SbtFiles(files) + + /** Includes settings automatically*/ + def seq(autos: AddSettings*): AddSettings = new Sequence(autos) + + val allDefaults: AddSettings = seq(userSettings, allPlugins, defaultSbtFiles) +} + diff --git a/main/Load.scala b/main/Load.scala index d7186bfe6..4c127d4cf 100755 --- a/main/Load.scala +++ b/main/Load.scala @@ -195,19 +195,25 @@ object Load loaded.units.toSeq.flatMap { case (uri, build) => val eval = if(uri == loaded.root) rootEval else lazyEval(build.unit) val plugins = build.unit.plugins.plugins - val (pluginSettings, pluginProjectSettings, pluginBuildSettings) = extractSettings(plugins) - val (pluginThisProject, pluginNotThis) = pluginSettings partition isProjectThis + val pluginBuildSettings = plugins.flatMap(_.buildSettings) + val pluginNotThis = plugins.flatMap(_.settings) filterNot isProjectThis val projectSettings = build.defined flatMap { case (id, project) => - val srcs = configurationSources(project.base) - val ref = ProjectRef(uri, id) - val defineConfig = for(c <- project.configurations) yield ( (configuration in (ref, ConfigKey(c.name))) :== c) val loader = build.unit.definitions.loader - val settings = - (thisProject :== project) +: - (thisProjectRef :== ref) +: - (defineConfig ++ project.settings ++ injectSettings.projectLoaded(loader) ++ pluginThisProject ++ - pluginProjectSettings ++ configurations(srcs, eval, build.imports)(loader) ++ injectSettings.project) - + lazy val defaultSbtFiles = configurationSources(project.base) + + import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,Sequence} + def expand(auto: AddSettings): Seq[Setting[_]] = auto match { + case User => injectSettings.projectLoaded(loader) + case sf: SbtFiles => configurations( sf.files.map(f => IO.resolve(project.base, f)), eval, build.imports )(loader) + case sf: DefaultSbtFiles => configurations( defaultSbtFiles.filter(sf.include), eval, build.imports )(loader) + case f: Plugins => plugins.filter(f.include).flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings) + case q: Sequence => q.sequence.flatMap(expand) + } + + val ref = ProjectRef(uri, id) + val defineConfig: Seq[Setting[_]] = for(c <- project.configurations) yield ( (configuration in (ref, ConfigKey(c.name))) :== c) + val builtin: Seq[Setting[_]] = (thisProject :== project) +: (thisProjectRef :== ref) +: defineConfig + val settings = builtin ++ project.settings ++ expand(project.auto) ++ injectSettings.project // map This to thisScope, Select(p) to mapRef(uri, rootProject, p) transformSettings(projectScope(ref), uri, rootProject, settings) } @@ -221,8 +227,11 @@ object Load loaded.units.toSeq flatMap { case (_, build) => build.unit.plugins.plugins flatMap { _.globalSettings } } + + @deprecated("No longer used.", "0.13.0") def extractSettings(plugins: Seq[Plugin]): (Seq[Setting[_]], Seq[Setting[_]], Seq[Setting[_]]) = (plugins.flatMap(_.settings), plugins.flatMap(_.projectSettings), plugins.flatMap(_.buildSettings)) + def transformProjectOnly(uri: URI, rootProject: URI => String, settings: Seq[Setting[_]]): Seq[Setting[_]] = Project.transform(Scope.resolveProject(uri, rootProject), settings) def transformSettings(thisScope: Scope, uri: URI, rootProject: URI => String, settings: Seq[Setting[_]]): Seq[Setting[_]] = diff --git a/main/Project.scala b/main/Project.scala index 1a40ca79a..f87eff749 100755 --- a/main/Project.scala +++ b/main/Project.scala @@ -24,6 +24,7 @@ sealed trait ProjectDefinition[PR <: ProjectReference] def dependencies: Seq[ClasspathDep[PR]] def uses: Seq[PR] = aggregate ++ dependencies.map(_.project) def referenced: Seq[PR] = delegates ++ uses + def auto: AddSettings override final def hashCode: Int = id.hashCode ^ base.hashCode ^ getClass.hashCode override final def equals(o: Any) = o match { @@ -34,23 +35,24 @@ sealed trait ProjectDefinition[PR <: ProjectReference] } 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[Project.Setting[_]] = settings, configurations: Seq[Configuration] = configurations): Project = - Project(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations) + def copy(id: String = id, base: File = base, aggregate: => Seq[ProjectReference] = aggregate, dependencies: => Seq[ClasspathDep[ProjectReference]] = dependencies, + delegates: => Seq[ProjectReference] = delegates, settings: => Seq[Project.Setting[_]] = settings, configurations: Seq[Configuration] = configurations, + auto: AddSettings = auto): Project = + Project(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto) def resolve(resolveRef: ProjectReference => ProjectRef): ResolvedProject = { def resolveRefs(prs: Seq[ProjectReference]) = prs map resolveRef 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) + resolved(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates), settings, configurations, auto) } def resolveBuild(resolveRef: ProjectReference => ProjectReference): Project = { def resolveRefs(prs: Seq[ProjectReference]) = prs map resolveRef def resolveDeps(ds: Seq[ClasspathDep[ProjectReference]]) = ds map resolveDep def resolveDep(d: ClasspathDep[ProjectReference]) = ClasspathDependency(resolveRef(d.project), d.configuration) - apply(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates), settings, configurations) + apply(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates), settings, configurations, auto) } def overrideConfigs(cs: Configuration*): Project = copy(configurations = Defaults.overrideConfigs(cs : _*)(configurations)) @@ -60,6 +62,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] def aggregate(refs: ProjectReference*): Project = copy(aggregate = (aggregate: Seq[ProjectReference]) ++ refs) def configs(cs: Configuration*): Project = copy(configurations = configurations ++ cs) def settings(ss: Project.Setting[_]*): Project = copy(settings = (settings: Seq[Project.Setting[_]]) ++ ss) + def autoSettings(select: AddSettings*): Project = copy(auto = AddSettings.seq(select : _*)) } sealed trait ResolvedProject extends ProjectDefinition[ProjectRef] @@ -138,8 +141,8 @@ object Project extends Init[Scope] with ProjectExtra case _ => display(project) + "/" } - private abstract class ProjectDef[PR <: ProjectReference](val id: String, val base: File, aggregate0: => Seq[PR], dependencies0: => Seq[ClasspathDep[PR]], delegates0: => Seq[PR], - settings0: => Seq[Setting[_]], val configurations: Seq[Configuration]) extends ProjectDefinition[PR] + private abstract class ProjectDef[PR <: ProjectReference](val id: String, val base: File, aggregate0: => Seq[PR], dependencies0: => Seq[ClasspathDep[PR]], + delegates0: => Seq[PR], settings0: => Seq[Setting[_]], val configurations: Seq[Configuration], val auto: AddSettings) extends ProjectDefinition[PR] { lazy val aggregate = aggregate0 lazy val dependencies = dependencies0 @@ -149,16 +152,17 @@ object Project extends Init[Scope] with ProjectExtra Dag.topologicalSort(configurations)(_.extendsConfigs) // checks for cyclic references here instead of having to do it in Scope.delegates } - def apply(id: String, base: File, aggregate: => Seq[ProjectReference] = Nil, dependencies: => Seq[ClasspathDep[ProjectReference]] = Nil, delegates: => Seq[ProjectReference] = Nil, - settings: => Seq[Setting[_]] = defaultSettings, configurations: Seq[Configuration] = Configurations.default): Project = + def apply(id: String, base: File, aggregate: => Seq[ProjectReference] = Nil, dependencies: => Seq[ClasspathDep[ProjectReference]] = Nil, + delegates: => Seq[ProjectReference] = Nil, settings: => Seq[Setting[_]] = defaultSettings, configurations: Seq[Configuration] = Configurations.default, + auto: AddSettings = AddSettings.allDefaults): Project = { DefaultParsers.parse(id, DefaultParsers.ID).left.foreach(errMsg => error("Invalid project ID: " + errMsg)) - new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations) with Project + new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations, auto) with Project } def resolved(id: String, base: File, aggregate: => Seq[ProjectRef], dependencies: => Seq[ResolvedClasspathDependency], delegates: => Seq[ProjectRef], - settings: Seq[Setting[_]], configurations: Seq[Configuration]): ResolvedProject = - new ProjectDef[ProjectRef](id, base, aggregate, dependencies, delegates, settings, configurations) with ResolvedProject + settings: Seq[Setting[_]], configurations: Seq[Configuration], auto: AddSettings): ResolvedProject = + new ProjectDef[ProjectRef](id, base, aggregate, dependencies, delegates, settings, configurations, auto) with ResolvedProject def defaultSettings: Seq[Setting[_]] = Defaults.defaultSettings diff --git a/sbt/src/sbt-test/project/auto-settings/b/build.sbt b/sbt/src/sbt-test/project/auto-settings/b/build.sbt new file mode 100644 index 000000000..e864169d1 --- /dev/null +++ b/sbt/src/sbt-test/project/auto-settings/b/build.sbt @@ -0,0 +1 @@ +version := "ignored" \ No newline at end of file diff --git a/sbt/src/sbt-test/project/auto-settings/d/build.sbt b/sbt/src/sbt-test/project/auto-settings/d/build.sbt new file mode 100644 index 000000000..7e86dd0fb --- /dev/null +++ b/sbt/src/sbt-test/project/auto-settings/d/build.sbt @@ -0,0 +1 @@ +version := "1.3" \ No newline at end of file diff --git a/sbt/src/sbt-test/project/auto-settings/e/build.sbt b/sbt/src/sbt-test/project/auto-settings/e/build.sbt new file mode 100644 index 000000000..e864169d1 --- /dev/null +++ b/sbt/src/sbt-test/project/auto-settings/e/build.sbt @@ -0,0 +1 @@ +version := "ignored" \ No newline at end of file diff --git a/sbt/src/sbt-test/project/auto-settings/explicit/a.txt b/sbt/src/sbt-test/project/auto-settings/explicit/a.txt new file mode 100644 index 000000000..873fd02b0 --- /dev/null +++ b/sbt/src/sbt-test/project/auto-settings/explicit/a.txt @@ -0,0 +1 @@ +version := "1.4" diff --git a/sbt/src/sbt-test/project/auto-settings/global/user.sbt b/sbt/src/sbt-test/project/auto-settings/global/user.sbt new file mode 100644 index 000000000..99be66402 --- /dev/null +++ b/sbt/src/sbt-test/project/auto-settings/global/user.sbt @@ -0,0 +1 @@ +version := "1.1" \ No newline at end of file diff --git a/sbt/src/sbt-test/project/auto-settings/project/P.scala b/sbt/src/sbt-test/project/auto-settings/project/P.scala new file mode 100644 index 000000000..eddc49a76 --- /dev/null +++ b/sbt/src/sbt-test/project/auto-settings/project/P.scala @@ -0,0 +1,31 @@ + import sbt._ + import Keys._ + + import AddSettings._ + +object B extends Build +{ + // version should be from explicit/a.txt + lazy val root = project("root", "1.4") autoSettings( userSettings, sbtFiles(file("explicit/a.txt")) ) + + // version should be from global/user.sbt + lazy val a = project("a", "1.1") autoSettings( userSettings ) + + // version should be the default 0.1-SNAPSHOT + lazy val b = project("b", "0.1-SNAPSHOT") autoSettings() + + // version should be from the explicit settings call + lazy val c = project("c", "0.9") settings(version := "0.9") autoSettings() + + // version should be from d/build.sbt + lazy val d = project("d", "1.3") settings(version := "0.9") autoSettings( defaultSbtFiles ) + + // version should be from global/user.sbt + lazy val e = project("e", "1.1") settings(version := "0.9") autoSettings( defaultSbtFiles, sbtFiles(file("../explicit/a.txt")), userSettings ) + + def project(id: String, expectedVersion: String): Project = Project(id, if(id == "root") file(".") else file(id)) settings( + TaskKey[Unit]("check") <<= version map { v => + assert(v == expectedVersion, "Expected version '" + expectedVersion + "', got: " + v) + } + ) +} diff --git a/sbt/src/sbt-test/project/auto-settings/root.sbt b/sbt/src/sbt-test/project/auto-settings/root.sbt new file mode 100644 index 000000000..7ec0d973f --- /dev/null +++ b/sbt/src/sbt-test/project/auto-settings/root.sbt @@ -0,0 +1 @@ +version := "1.2" \ No newline at end of file diff --git a/sbt/src/sbt-test/project/auto-settings/test b/sbt/src/sbt-test/project/auto-settings/test new file mode 100644 index 000000000..249b0b2f9 --- /dev/null +++ b/sbt/src/sbt-test/project/auto-settings/test @@ -0,0 +1,11 @@ +> root/check + +> a/check + +> b/check + +> c/check + +> d/check + +> e/check \ No newline at end of file