diff --git a/build.sbt b/build.sbt index 5d03153b8..d02ec41d0 100644 --- a/build.sbt +++ b/build.sbt @@ -785,6 +785,9 @@ lazy val mainProj = (project in file("main")) exclude[IncompatibleSignatureProblem]("sbt.ProjectExtra.inConfig"), exclude[IncompatibleSignatureProblem]("sbt.ProjectExtra.inTask"), exclude[IncompatibleSignatureProblem]("sbt.ProjectExtra.inScope"), + + exclude[MissingTypesProblem]("sbt.internal.Load*"), + exclude[IncompatibleSignatureProblem]("sbt.internal.Load*"), ) ) .configure( diff --git a/main/src/main/scala/sbt/coursierint/CoursierInputsTasks.scala b/main/src/main/scala/sbt/coursierint/CoursierInputsTasks.scala index b437cfd32..a579e766c 100644 --- a/main/src/main/scala/sbt/coursierint/CoursierInputsTasks.scala +++ b/main/src/main/scala/sbt/coursierint/CoursierInputsTasks.scala @@ -45,6 +45,8 @@ object CoursierInputsTasks { sv: String, sbv: String, auOpt: Option[URL], + description: String, + homepage: Option[URL], log: Logger ): CProject = { @@ -63,12 +65,15 @@ object CoursierInputsTasks { case (config, dep) => (config, dep.withExclusions(dep.exclusions ++ exclusions0)) }) - auOpt match { + val proj2 = auOpt match { case Some(au) => val props = proj1.properties :+ ("info.apiURL" -> au.toString) proj1.withProperties(props) case _ => proj1 } + proj2.withInfo( + proj2.info.withDescription(description).withHomePage(homepage.fold("")(_.toString)) + ) } def coursierProjectTask: Def.Initialize[sbt.Task[CProject]] = @@ -81,6 +86,8 @@ object CoursierInputsTasks { scalaVersion.value, scalaBinaryVersion.value, apiURL.value, + description.value, + homepage.value, streams.value.log ) } diff --git a/main/src/main/scala/sbt/internal/AddSettings.scala b/main/src/main/scala/sbt/internal/AddSettings.scala index 2fd4a6667..14acc5c05 100644 --- a/main/src/main/scala/sbt/internal/AddSettings.scala +++ b/main/src/main/scala/sbt/internal/AddSettings.scala @@ -18,11 +18,15 @@ import java.io.File sealed abstract class AddSettings object AddSettings { - private[sbt] final class Sequence(val sequence: Seq[AddSettings]) extends AddSettings + private[sbt] final class Sequence(val sequence: Seq[AddSettings]) extends AddSettings { + override def toString: String = s"Sequence($sequence)" + } private[sbt] final object User extends AddSettings private[sbt] final class AutoPlugins(val include: AutoPlugin => 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 + private[sbt] final class SbtFiles(val files: Seq[File]) extends AddSettings { + override def toString: String = s"SbtFiles($files)" + } private[sbt] final object BuildScalaFiles extends AddSettings /** Adds all settings from autoplugins. */ diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index 837730bbf..12b94188c 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -421,6 +421,13 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable { status = Stopped(Some(thread)) thread.interrupt() case Stopped(threadOption) => + // sleep to avoid consuming a lot of CPU + try { + Thread.sleep(10) + } catch { + case e: InterruptedException => + Thread.currentThread().interrupt(); + } // try to interrupt again! woot! threadOption.foreach(_.interrupt()) } diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 63a090a9e..622d41cd6 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -814,25 +814,27 @@ private[sbt] object Load { * 1. The first `Project` instance we encounter defines AddSettings and gets to specify where we pull other settings. * 2. Any project manipulation (enable/disablePlugins) is ok to be added in the order we encounter it. * - * Any further setting is ignored, as even the SettingSet API should be deprecated/removed with sbt 1.0. + * Any further setting is ignored. * * Note: Lots of internal details in here that shouldn't be otherwise exposed. * - * @param newProjects A sequence of projects we have not yet loaded, but will try to. Must not be Nil + * TODO - We want to attach the known (at this time) vals/lazy vals defined in each project's + * build.sbt to that project so we can later use this for the `set` command. + * + * @param newProjects A sequence of projects we have not yet loaded, but will try to. * @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 acc An accumulated list of loaded projects. TODO - how do these differ from newProjects? + * @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. - * @param makeOrDiscoverRoot True if we should autogenerate a root project. + * @param makeOrDiscoverRoot True if we should auto-generate a root project. * @param buildUri The URI of the build this is loading * @param context The plugin management context for autogenerated IDs. + * @param generatedConfigClassFiles + * @param extraSbtFiles * @return The completely resolved/updated sequence of projects defined, with all settings expanded. - * - * TODO - We want to attach the known (at this time) vals/lazy vals defined in each project's - * build.sbt to that project so we can later use this for the `set` command. */ private[this] def loadTransitive( newProjects: Seq[Project], @@ -850,9 +852,37 @@ private[sbt] object Load { extraSbtFiles: Seq[File] ): LoadedProjects = /*timed(s"Load.loadTransitive(${ newProjects.map(_.id) })", log)*/ { + + def load(newProjects: Seq[Project], acc: Seq[Project], generated: Seq[File]) = { + loadTransitive( + newProjects, + buildBase, + plugins, + eval, + injectSettings, + acc, + memoSettings, + log, + false, + buildUri, + context, + generated, + Nil + ) + } + // 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) + def discover(base: File): DiscoveredProjects = { + val auto = + if (base == buildBase) AddSettings.allDefaults + else AddSettings.defaultSbtFiles + + val extraFiles = + if (base == buildBase && isMetaBuildContext(context)) extraSbtFiles + else Nil + discoverProjects(auto, base, extraFiles, plugins, eval, memoSettings) + } + // Step two: // a. Apply all the project manipulations from .sbt files in order // b. Deduce the auto plugins for the project @@ -860,44 +890,40 @@ private[sbt] object Load { def finalizeProject( p: Project, files: Seq[File], + extraFiles: 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 configFiles = files.flatMap(f => memoSettings.get(f)) + val p1: Project = Function.chain(configFiles.flatMap(_.manipulations))(p) val autoPlugins: Seq[AutoPlugin] = try plugins.detected.deducePluginsFromProject(p1, log) catch { case e: AutoPluginException => throw translateAutoPluginException(e, p) } - val extra = - if (context.globalPluginProject) extraSbtFiles - else Nil val p2 = - this.resolveProject(p1, autoPlugins, plugins, injectSettings, memoSettings, extra, log) + resolveProject(p1, autoPlugins, plugins, injectSettings, memoSettings, extraFiles, log) val projectLevelExtra = - if (expand) autoPlugins flatMap { - _.derivedProjects(p2) map { _.setProjectOrigin(ProjectOrigin.DerivedProject) } + 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. - def discoverAndLoad(p: Project): (Project, Seq[Project], Seq[File]) = { - val (root, discovered, files, generated) = - discover(AddSettings.allDefaults, p.base) match { - case DiscoveredProjects(Some(root), rest, files, generated) => - // TODO - We assume here the project defined in a build.sbt WINS because the original was - // a phony. However, we may want to 'merge' the two, or only do this if the original was a default - // generated project. - (root, rest, files, generated) - case DiscoveredProjects(None, rest, files, generated) => (p, rest, files, generated) - } - val (finalRoot, projectLevelExtra) = - finalizeProject(root, files, true) - (finalRoot, discovered ++ projectLevelExtra, generated) + def discoverAndLoad(p: Project, rest: Seq[Project]): LoadedProjects = { + val DiscoveredProjects(rootOpt, discovered, files, extraFiles, generated) = discover( + p.base + ) + + // TODO: We assume here the project defined in a build.sbt WINS because the original was a + // phony. However, we may want to 'merge' the two, or only do this if the original was a + // default generated project. + val root = rootOpt.getOrElse(p) + val (finalRoot, projectLevelExtra) = finalizeProject(root, files, extraFiles, true) + val newProjects = rest ++ discovered ++ projectLevelExtra + val newAcc = acc :+ finalRoot + val newGenerated = generated ++ generatedConfigClassFiles + load(newProjects, newAcc, newGenerated) } // Load all config files AND finalize the project at the root directory, if it exists. @@ -905,92 +931,43 @@ private[sbt] object Load { newProjects match { case Seq(next, rest @ _*) => log.debug(s"[Loading] Loading project ${next.id} @ ${next.base}") - val (finished, discovered, generated) = discoverAndLoad(next) - loadTransitive( - rest ++ discovered, - buildBase, - plugins, - eval, - injectSettings, - acc :+ finished, - memoSettings, - log, - false, - buildUri, - context, - generated ++ generatedConfigClassFiles, - Nil - ) + discoverAndLoad(next, rest) case Nil if makeOrDiscoverRoot => - log.debug(s"[Loading] Scanning directory ${buildBase}") - val auto = - if (context.globalPluginProject) - AddSettings.seq(AddSettings.defaultSbtFiles, AddSettings.sbtFiles(extraSbtFiles: _*)) - else AddSettings.defaultSbtFiles - discover(auto, 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, 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, - Nil - ) - // 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(",")}") + log.debug(s"[Loading] Scanning directory $buildBase") + val DiscoveredProjects(rootOpt, discovered, files, extraFiles, generated) = discover( + buildBase + ) + val discoveredIdsStr = discovered.map(_.id).mkString(",") + val (root, expand, moreProjects, otherProjects) = rootOpt match { + case Some(root) => + log.debug(s"[Loading] Found root project ${root.id} w/ remaining $discoveredIdsStr") + (root, true, discovered, LoadedProjects(Nil, Nil)) + 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 = loadTransitive( - discovered, - buildBase, - plugins, - eval, - injectSettings, - acc, - memoSettings, - log, - false, - buildUri, - context, - Nil, - Nil - ) - val otherGenerated = otherProjects.generatedConfigClassFiles - val existingIds = otherProjects.projects map (_.id) - val refs = existingIds map (id => ProjectRef(buildUri, id)) - val defaultID = autoID(buildBase, context, existingIds) - val root0 = + val otherProjects = load(discovered, acc, Nil) + val root = { + val existingIds = otherProjects.projects.map(_.id) + val defaultID = autoID(buildBase, context, existingIds) + val refs = existingIds.map(id => ProjectRef(buildUri, id)) if (discovered.isEmpty || java.lang.Boolean.getBoolean("sbt.root.ivyplugin")) BuildDef.defaultAggregatedProject(defaultID, buildBase, refs) else BuildDef.generatedRootWithoutIvyPlugin(defaultID, buildBase, refs) - 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) + (root, false, Nil, otherProjects) } + val (finalRoot, projectLevelExtra) = + timed(s"Load.loadTransitive: finalizeProject($root)", log) { + finalizeProject(root, files, extraFiles, expand) + } + val newProjects = moreProjects ++ projectLevelExtra + val newAcc = finalRoot +: (acc ++ otherProjects.projects) + val newGenerated = + generated ++ otherProjects.generatedConfigClassFiles ++ generatedConfigClassFiles + load(newProjects, newAcc, newGenerated) case Nil => - log.debug( - s"[Loading] Done in ${buildBase}, returning: ${acc.map(_.id).mkString("(", ", ", ")")}" - ) + val projectIds = acc.map(_.id).mkString("(", ", ", ")") + log.debug(s"[Loading] Done in $buildBase, returning: $projectIds") LoadedProjects(acc, generatedConfigClassFiles) } } @@ -1014,6 +991,7 @@ private[sbt] object Load { root: Option[Project], nonRoot: Seq[Project], sbtFiles: Seq[File], + extraSbtFiles: Seq[File], generatedFiles: Seq[File] ) @@ -1048,6 +1026,7 @@ private[sbt] object Load { val allSettings = { // TODO - This mechanism of applying settings could be off... It's in two places now... lazy val defaultSbtFiles = configurationSources(p.base) + lazy val sbtFiles = defaultSbtFiles ++ extraSbtFiles // Filter the AutoPlugin settings we included based on which ones are // intended in the AddSettings.AutoPlugins filter. def autoPluginSettings(f: AutoPlugins) = @@ -1069,23 +1048,14 @@ private[sbt] object Load { 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(defaultSbtFiles.filter(sf.include)) + 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 = - if (extraSbtFiles.nonEmpty) - AddSettings.seq( - AddSettings.autoPlugins, - AddSettings.buildScalaFiles, - AddSettings.userSettings, - AddSettings.defaultSbtFiles, - AddSettings.sbtFiles(extraSbtFiles: _*), - ) - else AddSettings.allDefaults + val auto = AddSettings.allDefaults expandSettings(auto) } // Finally, a project we can use in buildStructure. @@ -1105,6 +1075,7 @@ private[sbt] object Load { private[this] def discoverProjects( auto: AddSettings, projectBase: File, + extraSbtFiles: Seq[File], loadedPlugins: LoadedPlugins, eval: () => Eval, memoSettings: mutable.Map[File, LoadedSbtFile] @@ -1112,6 +1083,7 @@ private[sbt] object Load { // Default sbt files to read, if needed lazy val defaultSbtFiles = configurationSources(projectBase) + lazy val sbtFiles = defaultSbtFiles ++ extraSbtFiles // Classloader of the build val loader = loadedPlugins.loader @@ -1148,7 +1120,7 @@ private[sbt] object Load { import AddSettings.{ DefaultSbtFiles, SbtFiles, Sequence } def associatedFiles(auto: AddSettings): Seq[File] = auto match { case sf: SbtFiles => sf.files.map(f => IO.resolve(projectBase, f)).filterNot(_.isHidden) - case sf: DefaultSbtFiles => defaultSbtFiles.filter(sf.include).filterNot(_.isHidden) + case sf: DefaultSbtFiles => sbtFiles.filter(sf.include).filterNot(_.isHidden) case q: Sequence => q.sequence.foldLeft(Seq.empty[File]) { (b, add) => b ++ associatedFiles(add) @@ -1160,7 +1132,13 @@ private[sbt] object Load { val rawProjects = loadedFiles.projects val (root, nonRoot) = rawProjects.partition(_.base == projectBase) // TODO - good error message if more than one root project - DiscoveredProjects(root.headOption, nonRoot, rawFiles, loadedFiles.generatedFiles) + DiscoveredProjects( + root.headOption, + nonRoot, + rawFiles, + extraSbtFiles, + loadedFiles.generatedFiles + ) } def globalPluginClasspath(globalPlugin: Option[GlobalPlugin]): Seq[Attributed[File]] = @@ -1215,11 +1193,19 @@ private[sbt] object Load { case None => config } - def plugins(dir: File, s: State, config: LoadBuildConfiguration): LoadedPlugins = - if (hasDefinition(dir)) + def plugins(dir: File, s: State, config: LoadBuildConfiguration): LoadedPlugins = { + val context = config.pluginManagement.context + val extraSbtFiles: Seq[File] = + if (isMetaBuildContext(context)) s.get(BasicKeys.extraMetaSbtFiles).getOrElse(Nil) + else Nil + if (hasDefinition(dir) || extraSbtFiles.nonEmpty) buildPlugins(dir, s, enableSbtPlugin(activateGlobalPlugin(config))) else noPlugins(dir, config) + } + + private def isMetaBuildContext(context: PluginManagement.Context): Boolean = + !context.globalPluginProject && context.pluginProjectDepth == 1 def hasDefinition(dir: File): Boolean = { import sbt.io.syntax._ diff --git a/main/src/main/scala/sbt/internal/librarymanagement/IvyXml.scala b/main/src/main/scala/sbt/internal/librarymanagement/IvyXml.scala index 9934d485c..95f5bb24f 100644 --- a/main/src/main/scala/sbt/internal/librarymanagement/IvyXml.scala +++ b/main/src/main/scala/sbt/internal/librarymanagement/IvyXml.scala @@ -104,6 +104,14 @@ object IvyXml { } } + val descriptionElem = { + val n = {project.info.description} + if (project.info.homePage.nonEmpty) + n % .attributes + else + n + } + val infoElem = { {licenseElems} - {project.info.description} + {descriptionElem} } % infoAttrs diff --git a/sbt/src/sbt-test/dependency-management/make-ivy-xml/build.sbt b/sbt/src/sbt-test/dependency-management/make-ivy-xml/build.sbt new file mode 100644 index 000000000..98c0eadcb --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/make-ivy-xml/build.sbt @@ -0,0 +1,24 @@ +import scala.xml.XML + +val descriptionValue = "This is just a test" +val homepageValue = "http://example.com" + +lazy val root = (project in file(".")) settings( + name := "ivy-xml-test", + description := descriptionValue, + homepage := Some(url(homepageValue)), + + TaskKey[Unit]("checkIvyXml") := { + val ivyXml = XML.loadFile(makeIvyXml.value) + val description = (ivyXml \ "info" \ "description").head + val homepage = (description \ "@homepage").head + + if (description.text != descriptionValue) + sys.error(s"Unexpected description: ${description.text}") + + if (homepage.text != homepageValue) + sys.error(s"Unexpected homepage: ${homepage.text}") + + () + } +) diff --git a/sbt/src/sbt-test/dependency-management/make-ivy-xml/test b/sbt/src/sbt-test/dependency-management/make-ivy-xml/test new file mode 100644 index 000000000..0c84f997a --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/make-ivy-xml/test @@ -0,0 +1 @@ +> checkIvyXml