![JAR search and dependency download from the Maven repository](/logo.png)
sbt.Project.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main_2.12 Show documentation
Show all versions of main_2.12 Show documentation
sbt is an interactive build tool
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
import java.io.File
import java.net.URI
import java.util.Locale
import Project._
import BasicKeys.serverLogLevel
import Keys.{
stateBuildStructure,
bspEnabled,
colorShellPrompt,
commands,
configuration,
historyPath,
projectCommand,
sessionSettings,
shellPrompt,
templateResolverInfos,
autoStartServer,
serverHost,
serverIdleTimeout,
serverLog,
serverPort,
serverUseJni,
serverAuthentication,
serverConnectionType,
fullServerHandlers,
logLevel,
windowsServerSecurityLevel,
}
import Scope.{ Global, ThisScope }
import sbt.SlashSyntax0._
import Def.{ Flattened, Initialize, ScopedKey, Setting }
import sbt.internal.{
Load,
BuildStructure,
LoadedBuild,
LoadedBuildUnit,
SettingGraph,
SettingCompletions,
SessionSettings
}
import sbt.internal.util.{ AttributeKey, AttributeMap, Dag, Relation, Settings, ~> }
import sbt.internal.util.Types.{ const, idFun }
import sbt.internal.util.complete.DefaultParsers
import sbt.internal.server.ServerHandler
import sbt.librarymanagement.Configuration
import sbt.util.{ Show, Level }
import sjsonnew.JsonFormat
import language.experimental.macros
import scala.concurrent.TimeoutException
import scala.concurrent.duration.FiniteDuration
sealed trait ProjectDefinition[PR <: ProjectReference] {
/**
* The project ID is used to uniquely identify a project within a build.
* It is used to refer to a project from the command line and in the scope of keys.
*/
def id: String
/** The base directory for the project.*/
def base: File
/**
* The configurations for this project. These are groups of related tasks and the main reason
* to list them here is when one configuration extends another. In this case, a setting lookup
* in one configuration will fall back to the configurations it extends configuration if the setting doesn't exist.
*/
def configurations: Seq[Configuration]
/**
* The explicitly defined sequence of settings that configure this project.
* These do not include the automatically appended settings as configured by `auto`.
*/
def settings: Seq[Setting[_]]
/**
* The references to projects that are aggregated by this project.
* When a task is run on this project, it will also be run on aggregated projects.
*/
def aggregate: Seq[PR]
/** The references to projects that are classpath dependencies of this project. */
def dependencies: Seq[ClasspathDep[PR]]
/** The references to projects that are aggregate and classpath dependencies of this project. */
def uses: Seq[PR] = aggregate ++ dependencies.map(_.project)
def referenced: Seq[PR] = uses
/**
* The defined [[Plugins]] associated with this project.
* A [[AutoPlugin]] is a common label that is used by plugins to determine what settings, if any, to add to a project.
*/
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]
override final def hashCode: Int = id.hashCode ^ base.hashCode ^ getClass.hashCode
override final def equals(o: Any) = o match {
case p: ProjectDefinition[_] => p.getClass == this.getClass && p.id == id && p.base == base
case _ => false
}
override def toString = {
val agg = ifNonEmpty("aggregate", aggregate)
val dep = ifNonEmpty("dependencies", dependencies)
val conf = ifNonEmpty("configurations", configurations)
val autos = ifNonEmpty("autoPlugins", autoPlugins.map(_.label))
val fields = s"id $id" :: s"base: $base" :: agg ::: dep ::: conf ::: (s"plugins: List($plugins)" :: autos)
s"Project(${fields.mkString(", ")})"
}
private[this] def ifNonEmpty[T](label: String, ts: Iterable[T]): List[String] =
if (ts.isEmpty) Nil else s"$label: $ts" :: Nil
}
trait CompositeProject {
def componentProjects: Seq[Project]
}
private[sbt] object CompositeProject {
/**
* Expand user defined projects with the component projects of `compositeProjects`.
*
* If two projects with the same id appear in the user defined projects and
* in `compositeProjects.componentProjects`, the user defined project wins.
* This is necessary for backward compatibility with the idioms:
* {{{
* lazy val foo = crossProject
* lazy val fooJS = foo.js.settings(...)
* lazy val fooJVM = foo.jvm.settings(...)
* }}}
* and the rarer:
* {{{
* lazy val fooJS = foo.js.settings(...)
* lazy val foo = crossProject
* lazy val fooJVM = foo.jvm.settings(...)
* }}}
*/
def expand(compositeProjects: Seq[CompositeProject]): Seq[Project] = {
val userProjects = compositeProjects.collect { case p: Project => p }
for (p <- compositeProjects.flatMap(_.componentProjects)) yield {
userProjects.find(_.id == p.id) match {
case Some(userProject) => userProject
case None => p
}
}
}.distinct
}
sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeProject {
def componentProjects: Seq[Project] = this :: Nil
private[sbt] def copy(
id: String = id,
base: File = base,
aggregate: Seq[ProjectReference] = aggregate,
dependencies: Seq[ClasspathDep[ProjectReference]] = dependencies,
settings: Seq[Setting[_]] = settings,
configurations: Seq[Configuration] = configurations
): Project =
copy2(id, base, aggregate, dependencies, settings, configurations)
private[this] def copy2(
id: String = id,
base: File = base,
aggregate: Seq[ProjectReference] = aggregate,
dependencies: Seq[ClasspathDep[ProjectReference]] = dependencies,
settings: Seq[Setting[_]] = settings,
configurations: Seq[Configuration] = configurations,
plugins: Plugins = plugins,
autoPlugins: Seq[AutoPlugin] = autoPlugins,
projectOrigin: ProjectOrigin = projectOrigin,
): Project =
unresolved(
id,
base,
aggregate = aggregate,
dependencies = dependencies,
settings = settings,
configurations,
plugins,
autoPlugins,
projectOrigin
)
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),
settings,
configurations,
plugins,
autoPlugins,
projectOrigin
)
}
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)
copy2(aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies))
}
/**
* 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.
* The intended use is a convenience for applying default configuration provided by a plugin.
*/
def configure(transforms: (Project => Project)*): Project = Function.chain(transforms)(this)
def withId(id: String) = copy(id = id)
/** Sets the base directory for this project.*/
def in(dir: File): Project = copy(base = dir)
/** Adds configurations to this project. Added configurations replace existing configurations with the same name.*/
def overrideConfigs(cs: Configuration*): Project =
copy(configurations = Defaults.overrideConfigs(cs: _*)(configurations))
/**
* Adds configuration at the *start* of the configuration list for this project. Previous configurations replace this prefix
* list with the same name.
*/
private[sbt] def prefixConfigs(cs: Configuration*): Project =
copy(configurations = Defaults.overrideConfigs(configurations: _*)(cs))
/** Adds new configurations directly to this project. To override an existing configuration, use `overrideConfigs`. */
def configs(cs: Configuration*): Project = copy(configurations = configurations ++ cs)
/** Adds classpath dependencies on internal or external projects. */
def dependsOn(deps: ClasspathDep[ProjectReference]*): Project =
copy(dependencies = dependencies ++ deps)
/**
* Adds projects to be aggregated. When a user requests a task to run on this project from the command line,
* the task will also be run in aggregated projects.
*/
def aggregate(refs: ProjectReference*): Project =
copy(aggregate = (aggregate: Seq[ProjectReference]) ++ refs)
/** Appends settings to the current settings sequence for this project. */
def settings(ss: Def.SettingsDefinition*): Project =
copy(settings = (settings: Seq[Def.Setting[_]]) ++ Def.settings(ss: _*))
/**
* Sets the [[AutoPlugin]]s of this project.
* A [[AutoPlugin]] is a common label that is used by plugins to determine what settings, if any, to enable on a project.
*/
def enablePlugins(ns: Plugins*): Project = setPlugins(ns.foldLeft(plugins)(Plugins.and))
/** Disable the given plugins on this project. */
def disablePlugins(ps: AutoPlugin*): Project =
setPlugins(Plugins.and(plugins, Plugins.And(ps.map(p => Plugins.Exclude(p)).toList)))
private[sbt] def setPlugins(ns: Plugins): Project = copy2(plugins = ns)
/** Definitively set the [[AutoPlugin]]s for this project. */
private[sbt] def setAutoPlugins(autos: Seq[AutoPlugin]): Project = copy2(autoPlugins = autos)
/** Definitively set the [[ProjectOrigin]] for this project. */
private[sbt] def setProjectOrigin(origin: ProjectOrigin): Project = copy2(projectOrigin = origin)
}
sealed trait ResolvedProject extends ProjectDefinition[ProjectRef] {
/** The [[AutoPlugin]]s enabled for this project as computed from [[plugins]].*/
def autoPlugins: Seq[AutoPlugin]
}
sealed trait ClasspathDep[PR <: ProjectReference] {
def project: PR; def configuration: Option[String]
}
final case class ResolvedClasspathDependency(project: ProjectRef, configuration: Option[String])
extends ClasspathDep[ProjectRef]
final case class ClasspathDependency(project: ProjectReference, configuration: Option[String])
extends ClasspathDep[ProjectReference]
object Project extends ProjectExtra {
private abstract class ProjectDef[PR <: ProjectReference](
val id: String,
val base: File,
val aggregate: Seq[PR],
val dependencies: Seq[ClasspathDep[PR]],
val settings: Seq[Def.Setting[_]],
val configurations: Seq[Configuration],
val plugins: Plugins,
val autoPlugins: Seq[AutoPlugin],
val projectOrigin: ProjectOrigin
) extends ProjectDefinition[PR] {
// checks for cyclic references here instead of having to do it in Scope.delegates
Dag.topologicalSort(configurations)(_.extendsConfigs)
}
def apply(id: String, base: File): Project =
unresolved(id, base, Nil, Nil, Nil, Nil, Plugins.empty, Nil, ProjectOrigin.Organic)
def showContextKey(state: State): Show[ScopedKey[_]] =
showContextKey(state, None)
def showContextKey(state: State, keyNameColor: Option[String]): Show[ScopedKey[_]] =
if (isProjectLoaded(state)) showContextKey2(session(state), keyNameColor)
else Def.showFullKey
@deprecated("Use showContextKey2 which doesn't take the unused structure param", "1.1.1")
def showContextKey(
session: SessionSettings,
structure: BuildStructure,
keyNameColor: Option[String] = None
): Show[ScopedKey[_]] =
showContextKey2(session, keyNameColor)
def showContextKey2(
session: SessionSettings,
keyNameColor: Option[String] = None
): Show[ScopedKey[_]] =
Def.showRelativeKey2(session.current, keyNameColor)
def showLoadingKey(
loaded: LoadedBuild,
keyNameColor: Option[String] = None
): Show[ScopedKey[_]] =
Def.showRelativeKey2(
ProjectRef(loaded.root, loaded.units(loaded.root).rootProjects.head),
keyNameColor
)
/** 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(s"Invalid project ID: $errMsg"))
val plugins = Plugins.empty
val origin = ProjectOrigin.GenericRoot
new ProjectDef(id, base, aggregate, Nil, Nil, Nil, plugins, Nil, origin)
with Project
with GeneratedRootProject
}
/** Returns None if `id` is a valid Project ID or Some containing the parser error message if it is not.*/
def validProjectID(id: String): Option[String] =
DefaultParsers.parse(id, DefaultParsers.ID).left.toOption
private[this] def validProjectIDStart(id: String): Boolean =
DefaultParsers.parse(id, DefaultParsers.IDStart).isRight
/** Constructs a valid Project ID based on `id` and returns it in Right or returns the error message in Left if one cannot be constructed.*/
def normalizeProjectID(id: String): Either[String, String] = {
val attempt = normalizeBase(id)
val refined =
if (attempt.length < 1) "root"
else if (!validProjectIDStart(attempt.substring(0, 1))) "root-" + attempt
else attempt
validProjectID(refined).toLeft(refined)
}
private[this] def normalizeBase(s: String) =
s.toLowerCase(Locale.ENGLISH).replaceAll("""\W+""", "-")
/**
* Normalize a String so that it is suitable for use as a dependency management module identifier.
* This is a best effort implementation, since valid characters are not documented or consistent.
*/
def normalizeModuleID(id: String): String = normalizeBase(id)
private def resolved(
id: String,
base: File,
aggregate: Seq[ProjectRef],
dependencies: Seq[ClasspathDep[ProjectRef]],
settings: Seq[Def.Setting[_]],
configurations: Seq[Configuration],
plugins: Plugins,
autoPlugins: Seq[AutoPlugin],
origin: ProjectOrigin
): ResolvedProject =
new ProjectDef[ProjectRef](
id,
base,
aggregate,
dependencies,
settings,
configurations,
plugins,
autoPlugins,
origin
) with ResolvedProject
private def unresolved(
id: String,
base: File,
aggregate: Seq[ProjectReference],
dependencies: Seq[ClasspathDep[ProjectReference]],
settings: Seq[Def.Setting[_]],
configurations: Seq[Configuration],
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,
settings,
configurations,
plugins,
autoPlugins,
origin
) with Project
}
final class Constructor(p: ProjectReference) {
def %(conf: Configuration): ClasspathDependency = %(conf.name)
def %(conf: String): ClasspathDependency = ClasspathDependency(p, Some(conf))
}
def getOrError[T](state: State, key: AttributeKey[T], msg: String): T =
state get key getOrElse sys.error(msg)
def structure(state: State): BuildStructure =
getOrError(state, stateBuildStructure, "No build loaded.")
def session(state: State): SessionSettings =
getOrError(state, sessionSettings, "Session not initialized.")
def isProjectLoaded(state: State): Boolean =
(state has sessionSettings) && (state has stateBuildStructure)
def extract(state: State): Extracted = extract(session(state), structure(state))
private[sbt] def extract(se: SessionSettings, st: BuildStructure): Extracted =
Extracted(st, se, se.current)(showContextKey2(se))
def getProjectForReference(ref: Reference, structure: BuildStructure): Option[ResolvedProject] =
ref match { case pr: ProjectRef => getProject(pr, structure); case _ => None }
def getProject(ref: ProjectRef, structure: BuildStructure): Option[ResolvedProject] =
getProject(ref, structure.units)
def getProject(ref: ProjectRef, structure: LoadedBuild): Option[ResolvedProject] =
getProject(ref, structure.units)
def getProject(ref: ProjectRef, units: Map[URI, LoadedBuildUnit]): Option[ResolvedProject] =
(units get ref.build).flatMap(_.defined get ref.project)
def runUnloadHooks(s: State): State = {
val previousOnUnload = orIdentity(s get Keys.onUnload.key)
previousOnUnload(s.runExitHooks())
}
def setProject(session: SessionSettings, structure: BuildStructure, s: State): State =
setProject(session, structure, s, identity)
def setProject(
session: SessionSettings,
structure: BuildStructure,
s: State,
preOnLoad: State => State
): State = {
val unloaded = runUnloadHooks(s)
val (onLoad, onUnload) = getHooks(structure.data)
val newAttrs = unloaded.attributes
.put(stateBuildStructure, structure)
.put(sessionSettings, session)
.put(Keys.onUnload.key, onUnload)
val newState = unloaded.copy(attributes = newAttrs)
// TODO: Fix this
onLoad(
preOnLoad(updateCurrent(newState)) /*LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)*/
)
}
def orIdentity[T](opt: Option[T => T]): T => T = opt getOrElse idFun
def getHook[T](key: SettingKey[T => T], data: Settings[Scope]): T => T =
orIdentity((Global / key) get data)
def getHooks(data: Settings[Scope]): (State => State, State => State) =
(getHook(Keys.onLoad, data), getHook(Keys.onUnload, data))
def current(state: State): ProjectRef = session(state).current
def updateCurrent(s: State): State = {
val structure = Project.structure(s)
val ref = Project.current(s)
Load.getProject(structure.units, ref.build, ref.project)
val msg = (ref / Keys.onLoadMessage) get structure.data getOrElse ""
if (!msg.isEmpty) s.log.info(msg)
def get[T](k: SettingKey[T]): Option[T] = (ref / k) get structure.data
def commandsIn(axis: ResolvedReference) = (axis / commands) get structure.data toList
val allCommands = commandsIn(ref) ++ commandsIn(BuildRef(ref.build)) ++ ((Global / commands) get structure.data toList)
val history = get(historyPath) flatMap idFun
val prompt = get(shellPrompt)
val newPrompt = get(colorShellPrompt)
val trs = ((Global / templateResolverInfos) get structure.data).toList.flatten
val startSvr: Option[Boolean] = get(autoStartServer)
val host: Option[String] = get(serverHost)
val port: Option[Int] = get(serverPort)
val enabledBsp: Option[Boolean] = get(bspEnabled)
val timeout: Option[Option[FiniteDuration]] = get(serverIdleTimeout)
val authentication: Option[Set[ServerAuthentication]] = get(serverAuthentication)
val connectionType: Option[ConnectionType] = get(serverConnectionType)
val srvLogLevel: Option[Level.Value] = (ref / serverLog / logLevel).get(structure.data)
val hs: Option[Seq[ServerHandler]] = get(ThisBuild / fullServerHandlers)
val commandDefs = allCommands.distinct.flatten[Command].map(_ tag (projectCommand, true))
val newDefinedCommands = commandDefs ++ BasicCommands.removeTagged(
s.definedCommands,
projectCommand
)
val winSecurityLevel = get(windowsServerSecurityLevel).getOrElse(2)
val useJni = get(serverUseJni).getOrElse(false)
val newAttrs =
s.attributes
.put(historyPath.key, history)
.put(windowsServerSecurityLevel.key, winSecurityLevel)
.put(serverUseJni.key, useJni)
.setCond(bspEnabled.key, enabledBsp)
.setCond(autoStartServer.key, startSvr)
.setCond(serverPort.key, port)
.setCond(serverHost.key, host)
.setCond(serverAuthentication.key, authentication)
.setCond(serverConnectionType.key, connectionType)
.setCond(serverIdleTimeout.key, timeout)
.put(historyPath.key, history)
.put(templateResolverInfos.key, trs)
.setCond(shellPrompt.key, prompt)
.setCond(colorShellPrompt.key, newPrompt)
.setCond(serverLogLevel, srvLogLevel)
.setCond(fullServerHandlers.key, hs)
s.copy(
attributes = newAttrs,
definedCommands = newDefinedCommands
)
}
def setCond[T](key: AttributeKey[T], vopt: Option[T], attributes: AttributeMap): AttributeMap =
attributes.setCond(key, vopt)
private[sbt] def checkTargets(data: Settings[Scope]): Option[String] = {
val dups = overlappingTargets(allTargets(data))
if (dups.isEmpty)
None
else {
val dupStrs = dups map {
case (dir, scopes) =>
s"${dir.getAbsolutePath}:\n\t${scopes.mkString("\n\t")}"
}
Some(s"Overlapping output directories:${dupStrs.mkString}")
}
}
private[this] def overlappingTargets(
targets: Seq[(ProjectRef, File)]
): Map[File, Seq[ProjectRef]] =
targets.groupBy(_._2).filter(_._2.size > 1).mapValues(_.map(_._1)).toMap
private[this] def allTargets(data: Settings[Scope]): Seq[(ProjectRef, File)] = {
import ScopeFilter._
val allProjects = ScopeFilter(Make.inAnyProject)
val targetAndRef = Def.setting { (Keys.thisProjectRef.value, Keys.target.value) }
new SettingKeyAll(Def.optional(targetAndRef)(idFun))
.all(allProjects)
.evaluate(data)
.flatMap(x => x)
}
def equal(a: ScopedKey[_], b: ScopedKey[_], mask: ScopeMask): Boolean =
a.key == b.key && Scope.equal(a.scope, b.scope, mask)
def fillTaskAxis(scoped: ScopedKey[_]): ScopedKey[_] =
ScopedKey(Scope.fillTaskAxis(scoped.scope, scoped.key), scoped.key)
def mapScope(f: Scope => Scope) = λ[ScopedKey ~> ScopedKey](k => ScopedKey(f(k.scope), k.key))
def transform(g: Scope => Scope, ss: Seq[Def.Setting[_]]): Seq[Def.Setting[_]] = {
val f = mapScope(g)
ss.map(_ mapKey f mapReferenced f)
}
def transformRef(g: Scope => Scope, ss: Seq[Def.Setting[_]]): Seq[Def.Setting[_]] = {
val f = mapScope(g)
ss.map(_ mapReferenced f)
}
def delegates(structure: BuildStructure, scope: Scope, key: AttributeKey[_]): Seq[ScopedKey[_]] =
structure.delegates(scope).map(d => ScopedKey(d, key))
def scopedKeyData(
structure: BuildStructure,
scope: Scope,
key: AttributeKey[_]
): Option[ScopedKeyData[_]] =
structure.data.get(scope, key) map { v =>
ScopedKeyData(ScopedKey(scope, key), v)
}
def details(structure: BuildStructure, actual: Boolean, scope: Scope, key: AttributeKey[_])(
implicit display: Show[ScopedKey[_]]
): String = {
val scoped = ScopedKey(scope, key)
val data = scopedKeyData(structure, scope, key) map { _.description } getOrElse {
"No entry for key."
}
val description = key.description match {
case Some(desc) => "Description:\n\t" + desc + "\n"; case None => ""
}
val definingScope = structure.data.definingScope(scope, key)
val providedBy = definingScope match {
case Some(sc) => "Provided by:\n\t" + Scope.display(sc, key.label) + "\n"
case None => ""
}
val definingScoped = definingScope match {
case Some(sc) => ScopedKey(sc, key); case None => scoped
}
val comp =
Def.compiled(structure.settings, actual)(structure.delegates, structure.scopeLocal, display)
val definedAt = comp get definingScoped map { c =>
Def.definedAtString(c.settings).capitalize
} getOrElse ""
val cMap = Def.flattenLocals(comp)
val related = cMap.keys.filter(k => k.key == key && k.scope != scope)
def derivedDependencies(c: ScopedKey[_]): List[ScopedKey[_]] =
comp
.get(c)
.map(_.settings.flatMap(s => if (s.isDerived) s.dependencies else Nil))
.toList
.flatten
val depends = cMap.get(scoped) match {
case Some(c) => c.dependencies.toSet; case None => Set.empty
}
val derivedDepends: Set[ScopedKey[_]] = derivedDependencies(definingScoped).toSet
val reverse = reverseDependencies(cMap, scoped)
val derivedReverse = reverse.filter(r => derivedDependencies(r).contains(definingScoped)).toSet
def printDepScopes(
baseLabel: String,
derivedLabel: String,
scopes: Iterable[ScopedKey[_]],
derived: Set[ScopedKey[_]]
): String = {
val label = s"$baseLabel${if (derived.isEmpty) "" else s" (D=$derivedLabel)"}"
val prefix: ScopedKey[_] => String =
if (derived.isEmpty) const("") else sk => if (derived(sk)) "D " else " "
printScopes(label, scopes, prefix = prefix)
}
def printScopes(
label: String,
scopes: Iterable[ScopedKey[_]],
max: Int = Int.MaxValue,
prefix: ScopedKey[_] => String = const("")
) =
if (scopes.isEmpty) ""
else {
val (limited, more) =
if (scopes.size <= max) (scopes, "\n") else (scopes.take(max), "\n...\n")
limited.map(sk => prefix(sk) + display.show(sk)).mkString(label + ":\n\t", "\n\t", more)
}
data + "\n" +
description +
providedBy +
definedAt +
printDepScopes("Dependencies", "derived from", depends, derivedDepends) +
printDepScopes("Reverse dependencies", "derives", reverse, derivedReverse) +
printScopes("Delegates", delegates(structure, scope, key)) +
printScopes("Related", related, 10)
}
def settingGraph(structure: BuildStructure, basedir: File, scoped: ScopedKey[_])(
implicit display: Show[ScopedKey[_]]
): SettingGraph =
SettingGraph(structure, basedir, scoped, 0)
def graphSettings(structure: BuildStructure, basedir: File)(
implicit display: Show[ScopedKey[_]]
): Unit = {
def graph(actual: Boolean, name: String) =
graphSettings(structure, actual, name, new File(basedir, name + ".dot"))
graph(true, "actual_dependencies")
graph(false, "declared_dependencies")
}
def graphSettings(structure: BuildStructure, actual: Boolean, graphName: String, file: File)(
implicit display: Show[ScopedKey[_]]
): Unit = {
val rel = relation(structure, actual)
val keyToString = display.show _
DotGraph.generateGraph(file, graphName, rel, keyToString, keyToString)
}
def relation(structure: BuildStructure, actual: Boolean)(
implicit display: Show[ScopedKey[_]]
): Relation[ScopedKey[_], ScopedKey[_]] =
relation(structure.settings, actual)(structure.delegates, structure.scopeLocal, display)
private[sbt] def relation(settings: Seq[Def.Setting[_]], actual: Boolean)(
implicit delegates: Scope => Seq[Scope],
scopeLocal: Def.ScopeLocal,
display: Show[ScopedKey[_]]
): Relation[ScopedKey[_], ScopedKey[_]] = {
val cMap = Def.flattenLocals(Def.compiled(settings, actual))
val emptyRelation = Relation.empty[ScopedKey[_], ScopedKey[_]]
cMap.foldLeft(emptyRelation) { case (r, (key, value)) => r + (key, value.dependencies) }
}
def showDefinitions(key: AttributeKey[_], defs: Seq[Scope])(
implicit display: Show[ScopedKey[_]]
): String =
showKeys(defs.map(scope => ScopedKey(scope, key)))
def showUses(defs: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String =
showKeys(defs)
private[this] def showKeys(s: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String =
s.map(display.show).sorted.mkString("\n\t", "\n\t", "\n\n")
def definitions(structure: BuildStructure, actual: Boolean, key: AttributeKey[_])(
implicit display: Show[ScopedKey[_]]
): Seq[Scope] =
relation(structure, actual)(display)._1s.toSeq flatMap { sk =>
if (sk.key == key) sk.scope :: Nil else Nil
}
def usedBy(structure: BuildStructure, actual: Boolean, key: AttributeKey[_])(
implicit display: Show[ScopedKey[_]]
): Seq[ScopedKey[_]] =
relation(structure, actual)(display).all.toSeq flatMap {
case (a, b) => if (b.key == key) List[ScopedKey[_]](a) else Nil
}
def reverseDependencies(
cMap: Map[ScopedKey[_], Flattened],
scoped: ScopedKey[_]
): Iterable[ScopedKey[_]] =
for ((key, compiled) <- cMap; dep <- compiled.dependencies if dep == scoped) yield key
def setAll(extracted: Extracted, settings: Seq[Def.Setting[_]]): SessionSettings =
SettingCompletions.setAll(extracted, settings).session
val ExtraBuilds = AttributeKey[List[URI]](
"extra-builds",
"Extra build URIs to load in addition to the ones defined by the project."
)
def extraBuilds(s: State): List[URI] = getOrNil(s, ExtraBuilds)
def getOrNil[T](s: State, key: AttributeKey[List[T]]): List[T] = s get key getOrElse Nil
def setExtraBuilds(s: State, extra: List[URI]): State = s.put(ExtraBuilds, extra)
def addExtraBuilds(s: State, extra: List[URI]): State =
setExtraBuilds(s, extra ::: extraBuilds(s))
def removeExtraBuilds(s: State, remove: List[URI]): State =
updateExtraBuilds(s, _.filterNot(remove.toSet))
def updateExtraBuilds(s: State, f: List[URI] => List[URI]): State =
setExtraBuilds(s, f(extraBuilds(s)))
// used by Coursier integration
private[sbt] def transitiveInterDependencies(
state: State,
projectRef: ProjectRef
): Seq[ProjectRef] = {
def dependencies(map: Map[ProjectRef, Seq[ProjectRef]], id: ProjectRef): Set[ProjectRef] = {
def helper(map: Map[ProjectRef, Seq[ProjectRef]], acc: Set[ProjectRef]): Set[ProjectRef] =
if (acc.exists(map.contains)) {
val (kept, rem) = map.partition { case (k, _) => acc(k) }
helper(rem, acc ++ kept.valuesIterator.flatten)
} else
acc
helper(map - id, map.getOrElse(id, Nil).toSet)
}
val allProjectsDeps: Map[ProjectRef, Seq[ProjectRef]] =
(for {
(p, ref) <- Project.structure(state).allProjectPairs
} yield ref -> p.dependencies.map(_.project)).toMap
val deps = dependencies(allProjectsDeps.toMap, projectRef)
Project.structure(state).allProjectRefs.filter(p => deps(p))
}
object LoadAction extends Enumeration {
val Return, Current, Plugins = Value
}
import LoadAction._
import DefaultParsers._
val loadActionParser = token(Space ~> ("plugins" ^^^ Plugins | "return" ^^^ Return)) ?? Current
val ProjectReturn =
AttributeKey[List[File]]("project-return", "Maintains a stack of builds visited using reload.")
def projectReturn(s: State): List[File] = getOrNil(s, ProjectReturn)
def inPluginProject(s: State): Boolean = projectReturn(s).length > 1
def setProjectReturn(s: State, pr: List[File]): State =
s.copy(attributes = s.attributes.put(ProjectReturn, pr))
def loadAction(s: State, action: LoadAction.Value): (State, File) = action match {
case Return =>
projectReturn(s) match {
case _ /* current */ :: returnTo :: rest =>
(setProjectReturn(s, returnTo :: rest), returnTo)
case _ => sys.error("Not currently in a plugin definition")
}
case Current =>
val base = s.configuration.baseDirectory
projectReturn(s) match {
case Nil => (setProjectReturn(s, base :: Nil), base); case x :: _ => (s, x)
}
case Plugins =>
val (newBase, oldStack) =
if (Project.isProjectLoaded(s))
(Project.extract(s).currentUnit.unit.plugins.base, projectReturn(s))
else // support changing to the definition project if it fails to load
(BuildPaths.projectStandard(s.baseDir), s.baseDir :: Nil)
val newS = setProjectReturn(s, newBase :: oldStack)
(newS, newBase)
}
def runTask[T](
taskKey: ScopedKey[Task[T]],
state: State,
checkCycles: Boolean = false
): Option[(State, Result[T])] = {
val extracted = Project.extract(state)
val ch = EvaluateTask.cancelStrategy(extracted, extracted.structure, state)
val p = EvaluateTask.executeProgress(extracted, extracted.structure, state)
val r = EvaluateTask.restrictions(state)
val fgc = EvaluateTask.forcegc(extracted, extracted.structure)
val mfi = EvaluateTask.minForcegcInterval(extracted, extracted.structure)
runTask(taskKey, state, EvaluateTaskConfig(r, checkCycles, p, ch, fgc, mfi))
}
def runTask[T](
taskKey: ScopedKey[Task[T]],
state: State,
config: EvaluateTaskConfig
): Option[(State, Result[T])] = {
val extracted = Project.extract(state)
EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, config)
}
def projectToRef(p: Project): ProjectReference = LocalProject(p.id)
implicit def projectToLocalProject(p: Project): LocalProject = LocalProject(p.id)
final class RichTaskSessionVar[S](i: Def.Initialize[Task[S]]) {
import SessionVar.{ persistAndSet, resolveContext, set, transform => tx }
def updateState(f: (State, S) => State): Def.Initialize[Task[S]] = i(t => tx(t, f))
def storeAs(key: TaskKey[S])(implicit f: JsonFormat[S]): Def.Initialize[Task[S]] = {
Keys.resolvedScoped.zipWith(i) { (scoped, task) =>
tx(
task,
(state, value) => persistAndSet(resolveContext(key, scoped.scope, state), state, value)(f)
)
}
}
def keepAs(key: TaskKey[S]): Def.Initialize[Task[S]] = {
i.zipWith(Keys.resolvedScoped) { (t, scoped) =>
tx(t, (state, value) => set(resolveContext(key, scoped.scope, state), state, value))
}
}
}
/** implicitly injected to tasks that return PromiseWrap.
*/
final class RichTaskPromise[A](i: Def.Initialize[Task[PromiseWrap[A]]]) {
import scala.concurrent.Await
import scala.concurrent.duration._
def await: Def.Initialize[Task[A]] = await(Duration.Inf)
def await(atMost: Duration): Def.Initialize[Task[A]] =
(Def
.task {
val p = i.value
var result: Option[A] = None
if (atMost == Duration.Inf) {
while (result.isEmpty) {
try {
result = Some(Await.result(p.underlying.future, Duration("1s")))
Thread.sleep(10)
} catch {
case _: TimeoutException => ()
}
}
} else {
result = Some(Await.result(p.underlying.future, atMost))
}
result.get
})
.tag(Tags.Sentinel)
}
import scala.reflect.macros._
def projectMacroImpl(c: blackbox.Context): c.Expr[Project] = {
import c.universe._
val enclosingValName = std.KeyMacro.definingValName(
c,
methodName =>
s"""$methodName must be directly assigned to a val, such as `val x = $methodName`. Alternatively, you can use `sbt.Project.apply`"""
)
val name = c.Expr[String](Literal(Constant(enclosingValName)))
reify { Project(name.splice, new File(name.splice)) }
}
}
private[sbt] trait GeneratedRootProject
trait ProjectExtra {
implicit def configDependencyConstructor[T](
p: T
)(implicit ev: T => ProjectReference): Constructor =
new Constructor(p)
implicit def classpathDependency[T](
p: T
)(implicit ev: T => ProjectReference): ClasspathDependency = ClasspathDependency(p, None)
// These used to be in Project so that they didn't need to get imported (due to Initialize being nested in Project).
// Moving Initialize and other settings types to Def and decoupling Project, Def, and Structure means these go here for now
implicit def richInitializeTask[T](init: Initialize[Task[T]]): Scoped.RichInitializeTask[T] =
new Scoped.RichInitializeTask(init)
implicit def richInitializeInputTask[T](
init: Initialize[InputTask[T]]
): Scoped.RichInitializeInputTask[T] =
new Scoped.RichInitializeInputTask(init)
implicit def richInitialize[T](i: Initialize[T]): Scoped.RichInitialize[T] =
new Scoped.RichInitialize[T](i)
implicit def richTaskSessionVar[T](init: Initialize[Task[T]]): Project.RichTaskSessionVar[T] =
new Project.RichTaskSessionVar(init)
implicit def sbtRichTaskPromise[A](
i: Initialize[Task[PromiseWrap[A]]]
): Project.RichTaskPromise[A] =
new Project.RichTaskPromise(i)
def inThisBuild(ss: Seq[Setting[_]]): Seq[Setting[_]] =
inScope(ThisScope.copy(project = Select(ThisBuild)))(ss)
def inConfig(conf: Configuration)(ss: Seq[Setting[_]]): Seq[Setting[_]] =
inScope(ThisScope.copy(config = Select(conf)))((configuration :== conf) +: ss)
def inTask(t: Scoped)(ss: Seq[Setting[_]]): Seq[Setting[_]] =
inScope(ThisScope.copy(task = Select(t.key)))(ss)
def inScope(scope: Scope)(ss: Seq[Setting[_]]): Seq[Setting[_]] =
Project.transform(Scope.replaceThis(scope), ss)
private[sbt] def inThisBuild[T](i: Initialize[T]): Initialize[T] =
inScope(ThisScope.copy(project = Select(ThisBuild)), i)
private[sbt] def inConfig[T](conf: Configuration, i: Initialize[T]): Initialize[T] =
inScope(ThisScope.copy(config = Select(conf)), i)
private[sbt] def inTask[T](t: Scoped, i: Initialize[T]): Initialize[T] =
inScope(ThisScope.copy(task = Select(t.key)), i)
private[sbt] def inScope[T](scope: Scope, i: Initialize[T]): Initialize[T] =
i mapReferenced Project.mapScope(Scope.replaceThis(scope))
/**
* Creates a new Project. This is a macro that expects to be assigned directly to a val.
* The name of the val is used as the project ID and the name of the base directory of the project.
*/
def project: Project = macro Project.projectMacroImpl
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy