diff --git a/main-settings/src/main/scala/sbt/Project.scala b/main-settings/src/main/scala/sbt/Project.scala index 8960596ad..2713277de 100644 --- a/main-settings/src/main/scala/sbt/Project.scala +++ b/main-settings/src/main/scala/sbt/Project.scala @@ -65,6 +65,8 @@ sealed trait ProjectDefinition[PR <: ProjectReference] { /** The [[AutoPlugin]]s enabled for this project. This value is only available on a loaded Project. */ private[sbt] def autoPlugins: Seq[AutoPlugin] + private[sbt] def commonSettings: Seq[Setting[_]] + override final def hashCode: Int = id.hashCode ^ base.hashCode ^ getClass.hashCode override final def equals(o: Any) = o match { @@ -161,6 +163,9 @@ sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeP /** Definitively set the [[ProjectOrigin]] for this project. */ private[sbt] def setProjectOrigin(origin: ProjectOrigin): Project = copy(projectOrigin = origin) + private[sbt] def setCommonSettings(settings: Seq[Setting[_]]): Project = + copy(commonSettings = settings) + /** * Applies the given functions to this Project. * The second function is applied to the result of applying the first to this Project and so on. @@ -180,6 +185,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeP aggregate: Seq[ProjectReference] = aggregate, dependencies: Seq[ClasspathDep[ProjectReference]] = dependencies, settings: Seq[Setting[_]] = settings, + commonSettings: Seq[Setting[_]] = commonSettings, configurations: Seq[Configuration] = configurations, plugins: Plugins = plugins, autoPlugins: Seq[AutoPlugin] = autoPlugins, @@ -191,6 +197,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeP aggregate = aggregate, dependencies = dependencies, settings = settings, + commonSettings = commonSettings, configurations, plugins, autoPlugins, @@ -218,6 +225,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeP aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), settings, + commonSettings, configurations, plugins, autoPlugins, @@ -227,7 +235,7 @@ end Project object Project: def apply(id: String, base: File): Project = - unresolved(id, base, Nil, Nil, Nil, Nil, Plugins.empty, Nil, ProjectOrigin.Organic) + unresolved(id, base, Nil, Nil, Nil, Nil, Nil, Plugins.empty, Nil, ProjectOrigin.Organic) /** This is a variation of def apply that mixes in GeneratedRootProject. */ private[sbt] def mkGeneratedRoot( @@ -238,7 +246,7 @@ object Project: validProjectID(id).foreach(errMsg => sys.error(s"Invalid project ID: $errMsg")) val plugins = Plugins.empty val origin = ProjectOrigin.GenericRoot - new ProjectDef(id, base, aggregate, Nil, Nil, Nil, plugins, Nil, origin) + new ProjectDef(id, base, aggregate, Nil, Nil, Nil, Nil, plugins, Nil, origin) with Project with GeneratedRootProject @@ -248,6 +256,7 @@ object Project: val aggregate: Seq[PR], val dependencies: Seq[ClasspathDep[PR]], val settings: Seq[Def.Setting[_]], + val commonSettings: Seq[Def.Setting[_]], val configurations: Seq[Configuration], val plugins: Plugins, val autoPlugins: Seq[AutoPlugin], @@ -265,6 +274,7 @@ object Project: aggregate: Seq[ProjectReference], dependencies: Seq[ClasspathDep[ProjectReference]], settings: Seq[Def.Setting[_]], + commonSettings: Seq[Def.Setting[_]], configurations: Seq[Configuration], plugins: Plugins, autoPlugins: Seq[AutoPlugin], @@ -277,6 +287,7 @@ object Project: aggregate, dependencies, settings, + commonSettings, configurations, plugins, autoPlugins, @@ -291,6 +302,7 @@ object Project: aggregate: Seq[ProjectRef], dependencies: Seq[ClasspathDep[ProjectRef]], settings: Seq[Def.Setting[_]], + commonSettings: Seq[Def.Setting[_]], configurations: Seq[Configuration], plugins: Plugins, autoPlugins: Seq[AutoPlugin], @@ -302,6 +314,7 @@ object Project: aggregate, dependencies, settings, + commonSettings, configurations, plugins, autoPlugins, diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 1223a2cb3..302a87d1e 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -78,7 +78,8 @@ private[sbt] object Load { "JAVA_HOME" -> javaHome, ) val loader = getClass.getClassLoader - val classpath = Attributed.blankSeq(provider.mainClasspath ++ scalaProvider.jars) + val classpath = + Attributed.blankSeq(provider.mainClasspath.toIndexedSeq ++ scalaProvider.jars.toIndexedSeq) val ivyConfiguration = InlineIvyConfiguration() .withPaths(IvyPaths(baseDirectory, bootIvyHome(state.configuration))) @@ -339,7 +340,7 @@ private[sbt] object Load { val keys = Index.allKeys(settings) val attributeKeys = Index.attributeKeys(data) ++ keys.map(_.key) val scopedKeys = keys ++ data.allKeys((s, k) => ScopedKey(s, k)).toVector - val projectsMap = projects.mapValues(_.defined.keySet).toMap + val projectsMap = projects.view.mapValues(_.defined.keySet).toMap val configsMap: Map[String, Seq[Configuration]] = projects.values.flatMap(bu => bu.defined map { case (k, v) => (k, v.configurations) }).toMap val keyIndex = KeyIndex(scopedKeys.toVector, projectsMap, configsMap) @@ -403,7 +404,7 @@ private[sbt] object Load { yield ((ref / ConfigKey(c.name) / configuration) :== c) val builtin: Seq[Setting[_]] = (thisProject :== project) +: (thisProjectRef :== ref) +: defineConfig - val settings = builtin ++ project.settings ++ injectSettings.project + val settings = builtin ++ injectSettings.project ++ project.settings // map This to thisScope, Select(p) to mapRef(uri, rootProject, p) transformSettings(projectScope(ref), uri, rootProject, settings) } @@ -769,6 +770,7 @@ private[sbt] object Load { () => eval, config.injectSettings, Nil, + Nil, memoSettings, config.log, createRoot, @@ -886,7 +888,7 @@ private[sbt] object Load { * @param buildBase The `baseDirectory` for the entire build. * @param plugins A misnomer, this is actually the compiled BuildDefinition (classpath and such) for this project. * @param eval A mechanism of generating an "Eval" which can compile scala code for us. - * @param injectSettings Settings we need to inject into projects. + * @param machineWideUserSettings Settings we need to inject into projects. * @param acc An accumulated list of loaded projects, originally in newProjects. * @param memoSettings A recording of all sbt files that have been loaded so far. * @param log The logger used for this project. @@ -902,7 +904,8 @@ private[sbt] object Load { buildBase: File, plugins: LoadedPlugins, eval: () => Eval, - injectSettings: InjectSettings, + machineWideUserSettings: InjectSettings, + commonSettings: Seq[Setting[_]], acc: Seq[Project], memoSettings: mutable.Map[VirtualFile, LoadedSbtFile], log: Logger, @@ -914,14 +917,19 @@ private[sbt] object Load { converter: MappedFileConverter, ): LoadedProjects = /*timed(s"Load.loadTransitive(${ newProjects.map(_.id) })", log)*/ { - - def load(newProjects: Seq[Project], acc: Seq[Project], generated: Seq[Path]) = + def load( + newProjects: Seq[Project], + acc: Seq[Project], + generated: Seq[Path], + commonSettings0: Seq[Setting[_]], + ) = loadTransitive( newProjects, buildBase, plugins, eval, - injectSettings, + machineWideUserSettings, + commonSettings0, acc, memoSettings, log, @@ -966,7 +974,8 @@ private[sbt] object Load { p1, autoPlugins, plugins, - injectSettings, + commonSettings, + machineWideUserSettings, memoSettings, extraFiles, converter, @@ -995,12 +1004,12 @@ private[sbt] object Load { val newProjects = rest ++ discovered ++ projectLevelExtra val newAcc = acc :+ finalRoot val newGenerated = generated ++ generatedConfigClassFiles - load(newProjects, newAcc, newGenerated) + load(newProjects, newAcc, newGenerated, finalRoot.commonSettings) } // Load all config files AND finalize the project at the root directory, if it exists. // Continue loading if we find any more. - newProjects match { + newProjects match case Seq(next, rest @ _*) => log.debug(s"[Loading] Loading project ${next.id} @ ${next.base}") discoverAndLoad(next, rest) @@ -1018,7 +1027,7 @@ private[sbt] object Load { case None => log.debug(s"[Loading] Found non-root projects $discoveredIdsStr") // Here we do something interesting... We need to create an aggregate root project - val otherProjects = load(discovered, acc, Nil) + val otherProjects = load(discovered, acc, Nil, Nil) val root = { val existingIds = otherProjects.projects.map(_.id) val defaultID = autoID(buildBase, context, existingIds) @@ -1036,12 +1045,11 @@ private[sbt] object Load { val newAcc = finalRoot +: (acc ++ otherProjects.projects) val newGenerated = generated ++ otherProjects.generatedConfigClassFiles ++ generatedConfigClassFiles - load(newProjects, newAcc, newGenerated) + load(newProjects, newAcc, newGenerated, finalRoot.commonSettings) case Nil => val projectIds = acc.map(_.id).mkString("(", ", ", ")") log.debug(s"[Loading] Done in $buildBase, returning: $projectIds") LoadedProjects(acc, generatedConfigClassFiles) - } } private[this] def translateAutoPluginException( @@ -1085,7 +1093,8 @@ private[sbt] object Load { p: Project, projectPlugins: Seq[AutoPlugin], loadedPlugins: LoadedPlugins, - globalUserSettings: InjectSettings, + commonSettings0: Seq[Setting[_]], + machineWideUserSettings: InjectSettings, memoSettings: mutable.Map[VirtualFile, LoadedSbtFile], extraSbtFiles: Seq[VirtualFile], converter: MappedFileConverter, @@ -1094,47 +1103,70 @@ private[sbt] object Load { timed(s"Load.resolveProject(${p.id})", log) { import AddSettings._ val autoConfigs = projectPlugins.flatMap(_.projectConfigurations) - + val auto = AddSettings.allDefaults // 3. Use AddSettings instance to order all Setting[_]s appropriately - val allSettings = { + // Settings are ordered as: + // AutoPlugin settings, common settings, machine-wide settings + project.settings(...) + def allAutoPluginSettings: Seq[Setting[_]] = { + // Filter the AutoPlugin settings we included based on which ones are + // intended in the AddSettings.AutoPlugins filter. + def autoPluginSettings(f: AutoPlugins) = + projectPlugins.filter(f.include).flatMap(_.projectSettings) + // Expand the AddSettings instance into a real Seq[Setting[_]] we'll use on the project + def expandPluginSettings(auto: AddSettings): Seq[Setting[_]] = + auto match + case p: AutoPlugins => autoPluginSettings(p) + case q: Sequence => + q.sequence.foldLeft(Seq.empty[Setting[_]]) { (b, add) => + b ++ expandPluginSettings(add) + } + case _ => Nil + expandPluginSettings(auto) + } + def buildWideCommonSettings: Seq[Setting[_]] = { // TODO - This mechanism of applying settings could be off... It's in two places now... lazy val defaultSbtFiles = configurationSources(p.base.getCanonicalFile()) .map(_.getAbsoluteFile().toPath()) .map(converter.toVirtualFile) lazy val sbtFiles: Seq[VirtualFile] = defaultSbtFiles ++ extraSbtFiles - // Filter the AutoPlugin settings we included based on which ones are - // intended in the AddSettings.AutoPlugins filter. - def autoPluginSettings(f: AutoPlugins) = - projectPlugins.filter(f.include).flatMap(_.projectSettings) // Grab all the settings we already loaded from sbt files - def settings(files: Seq[VirtualFile]): Seq[Setting[_]] = { - if (files.nonEmpty) + def settings(files: Seq[VirtualFile]): Seq[Setting[_]] = + if files.nonEmpty then log.info( s"${files.map(_.name()).mkString(s"loading settings for project ${p.id} from ", ",", " ...")}" ) - for { + else () + for file <- files config <- memoSettings.get(file).toSeq setting <- config.settings - } yield setting - } + yield setting + def expandCommonSettings(auto: AddSettings): Seq[Setting[_]] = + auto match + case sf: DefaultSbtFiles => settings(sbtFiles.filter(sf.include)) + case q: Sequence => + q.sequence.foldLeft(Seq.empty[Setting[_]]) { (b, add) => + b ++ expandCommonSettings(add) + } + case _ => Nil + commonSettings0 ++ expandCommonSettings(auto) + } + def allProjectSettings: Seq[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 User => machineWideUserSettings.cachedProjectLoaded(loadedPlugins.loader) case BuildScalaFiles => p.settings - case User => globalUserSettings.cachedProjectLoaded(loadedPlugins.loader) - // case sf: SbtFiles => settings(sf.files.map(f => IO.resolve(p.base, f))) - case sf: DefaultSbtFiles => settings(sbtFiles.filter(sf.include)) - case p: AutoPlugins => autoPluginSettings(p) case q: Sequence => q.sequence.foldLeft(Seq.empty[Setting[_]]) { (b, add) => b ++ expandSettings(add) } - val auto = AddSettings.allDefaults + case _ => Nil expandSettings(auto) } // Finally, a project we can use in buildStructure. - p.copy(settings = allSettings) + p.copy(settings = allAutoPluginSettings ++ buildWideCommonSettings ++ allProjectSettings) + .setCommonSettings(buildWideCommonSettings) .setAutoPlugins(projectPlugins) .prefixConfigs(autoConfigs: _*) } @@ -1448,7 +1480,7 @@ private[sbt] object Load { final class EvaluatedConfigurations(val eval: Eval, val settings: Seq[Setting[_]]) - final case class InjectSettings( + case class InjectSettings( global: Seq[Setting[_]], project: Seq[Setting[_]], projectLoaded: ClassLoader => Seq[Setting[_]] diff --git a/sbt-app/src/sbt-test/project/bare-settings/build.sbt b/sbt-app/src/sbt-test/project/bare-settings/build.sbt new file mode 100644 index 000000000..b9e284136 --- /dev/null +++ b/sbt-app/src/sbt-test/project/bare-settings/build.sbt @@ -0,0 +1,13 @@ +lazy val check = taskKey[Unit]("") +lazy val root = (project in file(".")) +lazy val foo = project +lazy val bar = project + +def scala212 = "2.12.17" +scalaVersion := scala212 + +check := { + assert((root / scalaVersion).value == scala212) + assert((foo / scalaVersion).value == scala212) + assert((bar / scalaVersion).value == scala212) +}