sbt.Defaults.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
The newest version!
/*
* 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, PrintWriter }
import java.net.{ URI, URL }
import java.nio.file.{ Paths, Path => NioPath }
import java.util.Optional
import java.util.concurrent.TimeUnit
import lmcoursier.CoursierDependencyResolution
import lmcoursier.definitions.{ Configuration => CConfiguration }
import org.apache.ivy.core.module.descriptor.ModuleDescriptor
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.logging.log4j.core.{ Appender => XAppender }
import org.scalasbt.ipcsocket.Win32SecurityLevel
import sbt.Def.{ Initialize, ScopedKey, Setting, SettingsDefinition }
import sbt.Keys._
import sbt.OptionSyntax._
import sbt.Project.{
inConfig,
inScope,
inTask,
richInitialize,
richInitializeTask,
richTaskSessionVar,
sbtRichTaskPromise
}
import sbt.Scope.{ GlobalScope, ThisScope, fillTaskAxis }
import sbt.coursierint._
import sbt.internal.CommandStrings.ExportStream
import sbt.internal._
import sbt.internal.classpath.AlternativeZincUtil
import sbt.internal.inc.JavaInterfaceUtil._
import sbt.internal.inc.classpath.{ ClasspathFilter, ClasspathUtil }
import sbt.internal.inc.{ CompileOutput, MappedFileConverter, Stamps, ZincLmUtil, ZincUtil }
import sbt.internal.io.{ Source, WatchState }
import sbt.internal.librarymanagement.mavenint.{
PomExtraDependencyAttributes,
SbtPomExtraProperties
}
import sbt.internal.librarymanagement._
import sbt.internal.nio.{ CheckBuildSources, Globs }
import sbt.internal.server.{
BspCompileProgress,
BspCompileTask,
BuildServerProtocol,
BuildServerReporter,
Definition,
LanguageServerProtocol,
ServerHandler,
VirtualTerminal
}
import sbt.internal.testing.TestLogger
import sbt.internal.util.Attributed.data
import sbt.internal.util.Types._
import sbt.internal.util.{ Terminal => ITerminal, _ }
import sbt.internal.util.complete._
import sbt.io.Path._
import sbt.io._
import sbt.io.syntax._
import sbt.librarymanagement.Artifact.{ DocClassifier, SourceClassifier }
import sbt.librarymanagement.Configurations.{
Compile,
CompilerPlugin,
IntegrationTest,
Provided,
Runtime,
Test
}
import sbt.librarymanagement.CrossVersion.{ binarySbtVersion, binaryScalaVersion, partialVersion }
import sbt.librarymanagement._
import sbt.librarymanagement.ivy._
import sbt.librarymanagement.syntax._
import sbt.nio.FileStamp
import sbt.nio.Keys._
import sbt.nio.file.syntax._
import sbt.nio.file.{ FileTreeView, Glob, RecursiveGlob }
import sbt.nio.Watch
import sbt.std.TaskExtra._
import sbt.testing.{ AnnotatedFingerprint, Framework, Runner, SubclassFingerprint }
import sbt.util.CacheImplicits._
import sbt.util.InterfaceUtil.{ t2, toJavaFunction => f1 }
import sbt.util._
import sjsonnew._
import sjsonnew.support.scalajson.unsafe.Converter
import xsbti.compile.TastyFiles
import xsbti.{ FileConverter, Position }
import scala.annotation.nowarn
import scala.collection.immutable.ListMap
import scala.concurrent.duration._
import scala.util.control.NonFatal
import scala.xml.NodeSeq
// incremental compiler
import sbt.SlashSyntax0._
import sbt.internal.inc.{ Analysis, AnalyzingCompiler, ManagedLoggedReporter, ScalaInstance }
import xsbti.{ CrossValue, VirtualFile, VirtualFileRef }
import xsbti.compile.{
AnalysisContents,
ClassFileManagerType,
ClasspathOptionsUtil,
CompileAnalysis,
CompileOptions,
CompileOrder,
CompileResult,
CompileProgress,
CompilerCache,
Compilers,
DefinesClass,
IncOptions,
IncToolOptionsUtil,
Inputs,
MiniSetup,
PerClasspathEntryLookup,
PreviousResult,
Setup,
TransactionalManagerType
}
object Defaults extends BuildCommon {
final val CacheDirectoryName = "cache"
def configSrcSub(key: SettingKey[File]): Initialize[File] =
Def.setting {
(ThisScope.copy(config = Zero) / key).value / nameForSrc(configuration.value.name)
}
def nameForSrc(config: String) = if (config == Configurations.Compile.name) "main" else config
def prefix(config: String) = if (config == Configurations.Compile.name) "" else config + "-"
def lock(app: xsbti.AppConfiguration): xsbti.GlobalLock = LibraryManagement.lock(app)
def extractAnalysis[T](a: Attributed[T]): (T, CompileAnalysis) =
(a.data, a.metadata get Keys.analysis getOrElse Analysis.Empty)
def analysisMap[T](cp: Seq[Attributed[T]]): T => Option[CompileAnalysis] = {
val m = (for (a <- cp; an <- a.metadata get Keys.analysis) yield (a.data, an)).toMap
m.get _
}
private[sbt] def globalDefaults(ss: Seq[Setting[_]]): Seq[Setting[_]] =
Def.defaultSettings(inScope(GlobalScope)(ss))
def buildCore: Seq[Setting[_]] = thisBuildCore ++ globalCore
def thisBuildCore: Seq[Setting[_]] =
inScope(GlobalScope.copy(project = Select(ThisBuild)))(
Seq(
managedDirectory := baseDirectory.value / "lib_managed"
)
)
private[sbt] lazy val globalCore: Seq[Setting[_]] = globalDefaults(
defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq(
excludeFilter :== HiddenFileFilter,
fileInputs :== Nil,
fileInputIncludeFilter :== AllPassFilter.toNio,
fileInputExcludeFilter :== DirectoryFilter.toNio || HiddenFileFilter,
fileOutputIncludeFilter :== AllPassFilter.toNio,
fileOutputExcludeFilter :== NothingFilter.toNio,
inputFileStamper :== sbt.nio.FileStamper.Hash,
outputFileStamper :== sbt.nio.FileStamper.LastModified,
onChangedBuildSource :== SysProp.onChangedBuildSource,
clean := { () },
unmanagedFileStampCache :=
state.value.get(persistentFileStampCache).getOrElse(new sbt.nio.FileStamp.Cache),
managedFileStampCache := new sbt.nio.FileStamp.Cache,
) ++ globalIvyCore ++ globalJvmCore ++ Watch.defaults
) ++ globalSbtCore
private[sbt] lazy val globalJvmCore: Seq[Setting[_]] =
Seq(
compilerCache := state.value get Keys.stateCompilerCache getOrElse CompilerCache.fresh,
sourcesInBase :== true,
autoAPIMappings := false,
apiMappings := Map.empty,
autoScalaLibrary :== true,
managedScalaInstance :== true,
classpathEntryDefinesClass := { (file: File) =>
sys.error("use classpathEntryDefinesClassVF instead")
},
extraIncOptions :== Seq("JAVA_CLASS_VERSION" -> sys.props("java.class.version")),
allowMachinePath :== true,
reportAbsolutePath := true,
run / traceLevel :== 0,
runMain / traceLevel :== 0,
bgRun / traceLevel :== 0,
fgRun / traceLevel :== 0,
console / traceLevel :== Int.MaxValue,
consoleProject / traceLevel :== Int.MaxValue,
autoCompilerPlugins :== true,
scalaHome :== None,
apiURL := None,
releaseNotesURL := None,
javaHome :== None,
discoveredJavaHomes := CrossJava.discoverJavaHomes,
javaHomes :== ListMap.empty,
fullJavaHomes := CrossJava.expandJavaHomes(discoveredJavaHomes.value ++ javaHomes.value),
testForkedParallel :== false,
javaOptions :== Nil,
sbtPlugin :== false,
isMetaBuild :== false,
reresolveSbtArtifacts :== false,
crossPaths :== true,
sourcePositionMappers :== Nil,
packageSrc / artifactClassifier :== Some(SourceClassifier),
packageDoc / artifactClassifier :== Some(DocClassifier),
includeFilter :== NothingFilter,
unmanagedSources / includeFilter :== ("*.java" | "*.scala"),
unmanagedJars / includeFilter :== "*.jar" | "*.so" | "*.dll" | "*.jnilib" | "*.zip",
unmanagedResources / includeFilter :== AllPassFilter,
bgList := { bgJobService.value.jobs },
ps := psTask.value,
bgStop := bgStopTask.evaluated,
bgWaitFor := bgWaitForTask.evaluated,
bgCopyClasspath :== true,
closeClassLoaders :== SysProp.closeClassLoaders,
allowZombieClassLoaders :== true,
packageTimestamp :== Package.defaultTimestamp,
) ++ BuildServerProtocol.globalSettings
private[sbt] lazy val globalIvyCore: Seq[Setting[_]] =
Seq(
internalConfigurationMap :== Configurations.internalMap _,
credentials :== SysProp.sbtCredentialsEnv.toList,
exportJars :== false,
trackInternalDependencies :== TrackLevel.TrackAlways,
exportToInternal :== TrackLevel.TrackAlways,
useCoursier :== SysProp.defaultUseCoursier,
retrieveManaged :== false,
retrieveManagedSync :== false,
configurationsToRetrieve :== None,
scalaOrganization :== ScalaArtifacts.Organization,
scalaArtifacts :== ScalaArtifacts.Artifacts,
sbtResolver := {
val v = sbtVersion.value
if (v.endsWith("-SNAPSHOT") || v.contains("-bin-")) Classpaths.sbtMavenSnapshots
else Resolver.DefaultMavenRepository
},
sbtResolvers := {
// TODO: Remove Classpaths.typesafeReleases for sbt 2.x
// We need to keep it around for sbt 1.x to cross build plugins with sbt 0.13 - https://github.com/sbt/sbt/issues/4698
Vector(sbtResolver.value, Classpaths.sbtPluginReleases, Classpaths.typesafeReleases)
},
crossVersion :== Disabled(),
buildDependencies := Classpaths.constructBuildDependencies.value,
version :== "0.1.0-SNAPSHOT",
versionScheme :== None,
classpathTypes :== Set("jar", "bundle", "maven-plugin", "test-jar") ++ CustomPomParser.JarPackagings,
artifactClassifier :== None,
checksums := Classpaths.bootChecksums(appConfiguration.value),
conflictManager := ConflictManager.default,
pomExtra :== NodeSeq.Empty,
pomPostProcess :== idFun,
pomAllRepositories :== false,
pomIncludeRepository :== Classpaths.defaultRepositoryFilter,
updateOptions := UpdateOptions(),
forceUpdatePeriod :== None,
// coursier settings
csrExtraCredentials :== Nil,
csrLogger := LMCoursier.coursierLoggerTask.value,
csrMavenProfiles :== Set.empty,
csrReconciliations :== LMCoursier.relaxedForAllModules,
csrSameVersions := Seq(
ScalaArtifacts.Artifacts.map(a => InclExclRule(scalaOrganization.value, a)).toSet
)
)
/** Core non-plugin settings for sbt builds. These *must* be on every build or the sbt engine will fail to run at all. */
private[sbt] lazy val globalSbtCore: Seq[Setting[_]] = globalDefaults(
Seq(
outputStrategy :== None, // TODO - This might belong elsewhere.
buildStructure := Project.structure(state.value),
settingsData := buildStructure.value.data,
checkBuildSources / aggregate :== false,
checkBuildSources / changedInputFiles / aggregate := false,
checkBuildSources / Continuous.dynamicInputs := None,
checkBuildSources / fileInputs := CheckBuildSources.buildSourceFileInputs.value,
checkBuildSources := CheckBuildSources.needReloadImpl.value,
fileCacheSize := "128M",
trapExit :== true,
connectInput :== false,
cancelable :== true,
taskCancelStrategy := { state: State =>
if (cancelable.value) TaskCancellationStrategy.Signal
else TaskCancellationStrategy.Null
},
envVars :== Map.empty,
sbtVersion := appConfiguration.value.provider.id.version,
sbtBinaryVersion := binarySbtVersion(sbtVersion.value),
// `pluginCrossBuild` scoping is based on sbt-cross-building plugin.
// The idea here is to be able to define a `sbtVersion in pluginCrossBuild`, which
// directs the dependencies of the plugin to build to the specified sbt plugin version.
pluginCrossBuild / sbtVersion := sbtVersion.value,
onLoad := idFun[State],
onUnload := idFun[State],
onUnload := { s =>
try onUnload.value(s)
finally IO.delete(taskTemporaryDirectory.value)
},
// extraLoggers is deprecated
SettingKey[ScopedKey[_] => Seq[XAppender]]("extraLoggers") :== { _ =>
Nil
},
extraAppenders := {
val f = SettingKey[ScopedKey[_] => Seq[XAppender]]("extraLoggers").value
s =>
f(s).map {
case a: Appender => a
case a => new ConsoleAppenderFromLog4J(a.getName, a)
}
},
useLog4J :== SysProp.useLog4J,
watchSources :== Nil, // Although this is deprecated, it can't be removed or it breaks += for legacy builds.
skip :== false,
taskTemporaryDirectory := {
val base = BuildPaths.globalTaskDirectoryStandard(appConfiguration.value.baseDirectory)
val dir = IO.createUniqueDirectory(base)
ShutdownHooks.add(() => IO.delete(dir))
dir
},
onComplete := {
val tempDirectory = taskTemporaryDirectory.value
() => Clean.deleteContents(tempDirectory, _ => false)
},
turbo :== SysProp.turbo,
usePipelining :== SysProp.pipelining,
exportPipelining := usePipelining.value,
useSuperShell := { if (insideCI.value) false else ITerminal.console.isSupershellEnabled },
superShellThreshold :== SysProp.supershellThreshold,
superShellMaxTasks :== SysProp.supershellMaxTasks,
superShellSleep :== SysProp.supershellSleep.millis,
progressReports := {
val rs = EvaluateTask.taskTimingProgress.toVector ++ EvaluateTask.taskTraceEvent.toVector
rs map { Keys.TaskProgress(_) }
},
commandProgress := Seq(),
// progressState is deprecated
SettingKey[Option[ProgressState]]("progressState") := None,
Previous.cache := new Previous(
Def.streamsManagerKey.value,
Previous.references.value.getReferences
),
Previous.references :== new Previous.References,
concurrentRestrictions := defaultRestrictions.value,
parallelExecution :== true,
fileTreeView :== FileTreeView.default,
Continuous.dynamicInputs := Continuous.dynamicInputsImpl.value,
logBuffered :== false,
commands :== Nil,
showSuccess :== true,
showTiming :== true,
timingFormat :== Aggregation.defaultFormat,
aggregate :== true,
maxErrors :== 100,
fork :== false,
initialize :== {},
templateResolverInfos :== Nil,
templateDescriptions :== TemplateCommandUtil.defaultTemplateDescriptions,
templateRunLocal := templateRunLocalInputTask(runLocalTemplate).evaluated,
forcegc :== sys.props
.get("sbt.task.forcegc")
.map(java.lang.Boolean.parseBoolean)
.getOrElse(GCUtil.defaultForceGarbageCollection),
minForcegcInterval :== GCUtil.defaultMinForcegcInterval,
interactionService :== CommandLineUIService,
autoStartServer := true,
serverHost := "127.0.0.1",
serverIdleTimeout := Some(new FiniteDuration(7, TimeUnit.DAYS)),
serverPort := 5000 + (Hash
.toHex(Hash(appConfiguration.value.baseDirectory.toString))
.## % 1000),
serverConnectionType := ConnectionType.Local,
serverAuthentication := {
if (serverConnectionType.value == ConnectionType.Tcp) Set(ServerAuthentication.Token)
else Set()
},
serverHandlers :== Nil,
windowsServerSecurityLevel := Win32SecurityLevel.OWNER_DACL, // allows any owner logon session to access the server
serverUseJni := BootServerSocket.requiresJNI || SysProp.serverUseJni,
fullServerHandlers := Nil,
insideCI :== sys.env.contains("BUILD_NUMBER") ||
sys.env.contains("CI") || SysProp.ci,
// watch related settings
pollInterval :== Watch.defaultPollInterval,
canonicalInput :== true,
echoInput :== true,
terminal := state.value.get(terminalKey).getOrElse(Terminal(ITerminal.get)),
InstallSbtn.installSbtn := InstallSbtn.installSbtnImpl.evaluated,
InstallSbtn.installSbtn / aggregate := false,
) ++ LintUnused.lintSettings
++ DefaultBackgroundJobService.backgroundJobServiceSettings
++ RemoteCache.globalSettings
)
private[sbt] lazy val buildLevelJvmSettings: Seq[Setting[_]] = Seq(
exportPipelining := usePipelining.value,
rootPaths := {
val app = appConfiguration.value
val base = app.baseDirectory.getCanonicalFile
val boot = app.provider.scalaProvider.launcher.bootDirectory
val ih = app.provider.scalaProvider.launcher.ivyHome
val coursierCache = csrCacheDirectory.value
val javaHome = Paths.get(sys.props("java.home"))
Map(
"BASE" -> base.toPath,
"SBT_BOOT" -> boot.toPath,
"CSR_CACHE" -> coursierCache.toPath,
"IVY_HOME" -> ih.toPath,
"JAVA_HOME" -> javaHome,
)
},
fileConverter := MappedFileConverter(rootPaths.value, allowMachinePath.value),
sourcePositionMappers := Nil, // Never set a default sourcePositionMapper, see #6352! Whatever you are trying to solve, do it in the foldMappers method.
// The virtual file value cache needs to be global or sbt will run out of direct byte buffer memory.
classpathDefinesClassCache := VirtualFileValueCache.definesClassCache(fileConverter.value),
fullServerHandlers := {
Seq(
LanguageServerProtocol.handler(fileConverter.value),
BuildServerProtocol.handler(
loadedBuild.value,
bspFullWorkspace.value,
sbtVersion.value,
semanticdbEnabled.value,
semanticdbVersion.value
),
VirtualTerminal.handler,
) ++ serverHandlers.value :+ ServerHandler.fallback
},
timeWrappedStamper := Stamps
.timeWrapBinaryStamps(Stamps.uncachedStamps(fileConverter.value), fileConverter.value),
reusableStamper := {
val converter = fileConverter.value
val unmanagedCache = unmanagedFileStampCache.value
val managedCache = managedFileStampCache.value
val backing = timeWrappedStamper.value
new xsbti.compile.analysis.ReadStamps {
def getAllLibraryStamps()
: java.util.Map[xsbti.VirtualFileRef, xsbti.compile.analysis.Stamp] =
backing.getAllLibraryStamps()
def getAllProductStamps()
: java.util.Map[xsbti.VirtualFileRef, xsbti.compile.analysis.Stamp] =
backing.getAllProductStamps()
def getAllSourceStamps()
: java.util.Map[xsbti.VirtualFileRef, xsbti.compile.analysis.Stamp] =
new java.util.HashMap[xsbti.VirtualFileRef, xsbti.compile.analysis.Stamp]
def library(fr: xsbti.VirtualFileRef): xsbti.compile.analysis.Stamp = backing.library(fr)
def product(fr: xsbti.VirtualFileRef): xsbti.compile.analysis.Stamp = backing.product(fr)
def source(fr: xsbti.VirtualFile): xsbti.compile.analysis.Stamp = {
val path = converter.toPath(fr)
unmanagedCache
.get(path)
.orElse(managedCache.getOrElseUpdate(path, sbt.nio.FileStamper.Hash))
.map(_.stamp)
.getOrElse(backing.source(fr))
}
}
},
)
private[sbt] def toAbsoluteSource(fc: FileConverter)(pos: Position): Position = {
val newPath: Option[NioPath] = pos
.sourcePath()
.asScala
.flatMap { path =>
try {
Some(fc.toPath(VirtualFileRef.of(path)))
} catch {
// catch all to trap wierd path injected by compiler, users, or plugins
case NonFatal(_) => None
}
}
newPath
.map { path =>
new Position {
override def line(): Optional[Integer] = pos.line()
override def lineContent(): String = pos.lineContent()
override def offset(): Optional[Integer] = pos.offset()
override def pointer(): Optional[Integer] = pos.pointer()
override def pointerSpace(): Optional[String] = pos.pointerSpace()
override def sourcePath(): Optional[String] = Optional.of(path.toAbsolutePath.toString)
override def sourceFile(): Optional[File] =
(try {
Some(path.toFile.getAbsoluteFile)
} catch {
case NonFatal(_) => None
}).toOptional
override def startOffset(): Optional[Integer] = pos.startOffset()
override def endOffset(): Optional[Integer] = pos.endOffset()
override def startLine(): Optional[Integer] = pos.startLine()
override def startColumn(): Optional[Integer] = pos.startColumn()
override def endLine(): Optional[Integer] = pos.endLine()
override def endColumn(): Optional[Integer] = pos.endColumn()
}
}
.getOrElse(pos)
}
// csrCacheDirectory is scoped to ThisBuild to allow customization.
private[sbt] lazy val buildLevelIvySettings: Seq[Setting[_]] = Seq(
csrCacheDirectory := {
if (useCoursier.value) LMCoursier.defaultCacheLocation
else Classpaths.dummyCoursierDirectory(appConfiguration.value)
},
)
def defaultTestTasks(key: Scoped): Seq[Setting[_]] =
inTask(key)(
Seq(
tags := Seq(Tags.Test -> 1),
logBuffered := true
)
)
// TODO: This should be on the new default settings for a project.
def projectCore: Seq[Setting[_]] = Seq(
name := thisProject.value.id,
logManager := LogManager.defaults(extraAppenders.value, ConsoleOut.terminalOut),
onLoadMessage := (onLoadMessage or
Def.setting {
s"set current project to ${name.value} (in build ${thisProjectRef.value.build})"
}).value
)
// Appended to JvmPlugin.projectSettings
def paths: Seq[Setting[_]] = Seq(
baseDirectory := thisProject.value.base,
target := baseDirectory.value / "target",
// Use a different history path for jline3 because the jline2 format is
// incompatible. By sbt 1.4.0, we should consider revering this to t / ".history"
// and possibly rewriting the jline2 history in a jline3 compatible format if the
// history file is incompatible. For now, just use a different file to facilitate
// going back and forth between 1.3.x and 1.4.x.
historyPath := (historyPath or target(t => Option(t / ".history3"))).value,
sourceDirectory := baseDirectory.value / "src",
sourceManaged := crossTarget.value / "src_managed",
resourceManaged := crossTarget.value / "resource_managed",
// Adds subproject build.sbt files to the global list of build files to monitor
Scope.Global / checkBuildSources / pollInterval :==
new FiniteDuration(Int.MinValue, TimeUnit.MILLISECONDS),
Scope.Global / checkBuildSources / fileInputs += baseDirectory.value.toGlob / "*.sbt",
)
lazy val configPaths = sourceConfigPaths ++ resourceConfigPaths ++ outputConfigPaths
lazy val sourceConfigPaths = Seq(
sourceDirectory := configSrcSub(sourceDirectory).value,
sourceManaged := configSrcSub(sourceManaged).value,
scalaSource := sourceDirectory.value / "scala",
javaSource := sourceDirectory.value / "java",
unmanagedSourceDirectories := {
val isDotty = ScalaInstance.isDotty(scalaVersion.value)
val epochVersion = if (isDotty) "3" else "2"
makeCrossSources(
scalaSource.value,
javaSource.value,
scalaBinaryVersion.value,
epochVersion,
crossPaths.value
) ++
makePluginCrossSources(
sbtPlugin.value,
scalaSource.value,
(pluginCrossBuild / sbtBinaryVersion).value,
crossPaths.value
)
},
unmanagedSources / fileInputs := {
val include = (unmanagedSources / includeFilter).value
val filter = (unmanagedSources / excludeFilter).value match {
// Hidden files are already filtered out by the FileStamps method
case NothingFilter | HiddenFileFilter => include
case exclude => include -- exclude
}
val baseSources =
if (sourcesInBase.value) Globs(baseDirectory.value.toPath, recursive = false, filter) :: Nil
else Nil
unmanagedSourceDirectories.value
.map(d => Globs(d.toPath, recursive = true, filter)) ++ baseSources
},
unmanagedSources := (unmanagedSources / inputFileStamps).value.map(_._1.toFile),
managedSourceDirectories := Seq(sourceManaged.value),
managedSources := {
val stamper = inputFileStamper.value
val cache = managedFileStampCache.value
val res = generate(sourceGenerators).value
res.foreach { f =>
cache.putIfAbsent(f.toPath, stamper)
}
res
},
managedSourcePaths / outputFileStamper := sbt.nio.FileStamper.Hash,
managedSourcePaths := managedSources.value.map(_.toPath),
sourceGenerators :== Nil,
sourceDirectories := Classpaths
.concatSettings(unmanagedSourceDirectories, managedSourceDirectories)
.value,
sources := Classpaths.concatDistinct(unmanagedSources, managedSources).value
)
lazy val resourceConfigPaths = Seq(
resourceDirectory := sourceDirectory.value / "resources",
resourceManaged := configSrcSub(resourceManaged).value,
unmanagedResourceDirectories := Seq(resourceDirectory.value),
managedResourceDirectories := Seq(resourceManaged.value),
resourceDirectories := Classpaths
.concatSettings(unmanagedResourceDirectories, managedResourceDirectories)
.value,
unmanagedResources / fileInputs := {
val include = (unmanagedResources / includeFilter).value
val filter = (unmanagedResources / excludeFilter).value match {
// Hidden files are already filtered out by the FileStamps method
case NothingFilter | HiddenFileFilter => include
case exclude => include -- exclude
}
unmanagedResourceDirectories.value.map(d => Globs(d.toPath, recursive = true, filter))
},
unmanagedResources := (unmanagedResources / inputFileStamps).value.map(_._1.toFile),
resourceGenerators :== Nil,
resourceGenerators += Def.task {
PluginDiscovery.writeDescriptors(discoveredSbtPlugins.value, resourceManaged.value)
},
managedResources := generate(resourceGenerators).value,
resources := Classpaths.concat(managedResources, unmanagedResources).value
)
// This exists for binary compatibility and probably never should have been public.
def addBaseSources: Seq[Def.Setting[Task[Seq[File]]]] = Nil
lazy val outputConfigPaths: Seq[Setting[_]] = Seq(
classDirectory := crossTarget.value / (prefix(configuration.value.name) + "classes"),
backendOutput := {
val converter = fileConverter.value
val dir = classDirectory.value
converter.toVirtualFile(dir.toPath)
},
earlyOutput / artifactPath := configArtifactPathSetting(artifact, "early").value,
earlyOutput := {
val converter = fileConverter.value
val jar = (earlyOutput / artifactPath).value
converter.toVirtualFile(jar.toPath)
},
semanticdbTargetRoot := crossTarget.value / (prefix(configuration.value.name) + "meta"),
compileAnalysisTargetRoot := crossTarget.value / (prefix(configuration.value.name) + "zinc"),
earlyCompileAnalysisTargetRoot := crossTarget.value / (prefix(configuration.value.name) + "early-zinc"),
doc / target := crossTarget.value / (prefix(configuration.value.name) + "api")
)
// This is included into JvmPlugin.projectSettings
def compileBase = inTask(console)(compilersSetting :: Nil) ++ compileBaseGlobal ++ Seq(
useScalaReplJLine :== false,
scalaInstanceTopLoader := {
val topLoader = if (!useScalaReplJLine.value) {
// the JLineLoader contains the SbtInterfaceClassLoader
classOf[org.jline.terminal.Terminal].getClassLoader
} else classOf[Compilers].getClassLoader // the SbtInterfaceClassLoader
// Scala 2.10 shades jline in the console so we need to make sure that it loads a compatible
// jansi version. Because of the shading, console does not work with the thin client for 2.10.x.
if (scalaVersion.value.startsWith("2.10.")) new ClassLoader(topLoader) {
override protected def loadClass(name: String, resolve: Boolean): Class[_] = {
if (name.startsWith("org.fusesource")) throw new ClassNotFoundException(name)
super.loadClass(name, resolve)
}
}
else topLoader
},
scalaInstance := scalaInstanceTask.value,
crossVersion := (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled),
pluginCrossBuild / sbtBinaryVersion := binarySbtVersion(
(pluginCrossBuild / sbtVersion).value
),
// Use (sbtVersion in pluginCrossBuild) to pick the sbt module to depend from the plugin.
// Because `sbtVersion in pluginCrossBuild` can be scoped to project level,
// this setting needs to be set here too.
pluginCrossBuild / sbtDependency := {
val app = appConfiguration.value
val id = app.provider.id
val sv = (pluginCrossBuild / sbtVersion).value
val scalaV = (pluginCrossBuild / scalaVersion).value
val binVersion = (pluginCrossBuild / scalaBinaryVersion).value
val cross = id.crossVersionedValue match {
case CrossValue.Disabled => Disabled()
case CrossValue.Full => CrossVersion.full
case CrossValue.Binary => CrossVersion.binary
}
val base = ModuleID(id.groupID, id.name, sv).withCrossVersion(cross)
CrossVersion(scalaV, binVersion)(base).withCrossVersion(Disabled())
},
crossSbtVersions := Vector((pluginCrossBuild / sbtVersion).value),
crossTarget := makeCrossTarget(
target.value,
scalaVersion.value,
scalaBinaryVersion.value,
(pluginCrossBuild / sbtBinaryVersion).value,
sbtPlugin.value,
crossPaths.value
),
cleanIvy := IvyActions.cleanCachedResolutionCache(ivyModule.value, streams.value.log),
clean := clean.dependsOn(cleanIvy).value,
scalaCompilerBridgeBinaryJar := Def.settingDyn {
val sv = scalaVersion.value
if (ScalaArtifacts.isScala3(sv) || VersionNumber(sv)
.matchesSemVer(SemanticSelector(s"=2.13 >=${ZincLmUtil.scala2SbtBridgeStart}")))
fetchBridgeBinaryJarTask(sv)
else Def.task[Option[File]](None)
}.value,
scalaCompilerBridgeSource := ZincLmUtil.getDefaultBridgeSourceModule(scalaVersion.value),
auxiliaryClassFiles ++= {
if (ScalaArtifacts.isScala3(scalaVersion.value)) List(TastyFiles.instance)
else Nil
},
consoleProject / scalaCompilerBridgeBinaryJar := None,
consoleProject / scalaCompilerBridgeSource := ZincLmUtil.getDefaultBridgeSourceModule(
appConfiguration.value.provider.scalaProvider.version
),
classpathOptions := ClasspathOptionsUtil.noboot(scalaVersion.value),
console / classpathOptions := ClasspathOptionsUtil.replNoboot(scalaVersion.value),
)
// must be a val: duplication detected by object identity
private[this] lazy val compileBaseGlobal: Seq[Setting[_]] = globalDefaults(
Seq(
auxiliaryClassFiles :== Nil,
incOptions := IncOptions.of(),
// TODO: Kept for old Dotty plugin. Remove on sbt 2.x
classpathOptions :== ClasspathOptionsUtil.boot,
// TODO: Kept for old Dotty plugin. Remove on sbt 2.x
console / classpathOptions :== ClasspathOptionsUtil.repl,
compileOrder :== CompileOrder.Mixed,
javacOptions :== Nil,
scalacOptions :== Nil,
scalaVersion := appConfiguration.value.provider.scalaProvider.version,
derive(crossScalaVersions := Seq(scalaVersion.value)),
derive(compilersSetting),
derive(scalaBinaryVersion := binaryScalaVersion(scalaVersion.value))
)
)
def makeCrossSources(
scalaSrcDir: File,
javaSrcDir: File,
sv: String,
epochVersion: String,
cross: Boolean
): Seq[File] = {
if (cross)
Seq(
scalaSrcDir,
scalaSrcDir.getParentFile / s"${scalaSrcDir.name}-$sv",
scalaSrcDir.getParentFile / s"${scalaSrcDir.name}-$epochVersion",
javaSrcDir,
).distinct
else
Seq(scalaSrcDir, javaSrcDir)
}
def makeCrossSources(
scalaSrcDir: File,
javaSrcDir: File,
sv: String,
cross: Boolean
): Seq[File] = {
if (cross)
Seq(scalaSrcDir.getParentFile / s"${scalaSrcDir.name}-$sv", scalaSrcDir, javaSrcDir)
else
Seq(scalaSrcDir, javaSrcDir)
}
def makePluginCrossSources(
isPlugin: Boolean,
scalaSrcDir: File,
sbtBinaryV: String,
cross: Boolean
): Seq[File] = {
if (cross && isPlugin)
Vector(scalaSrcDir.getParentFile / s"${scalaSrcDir.name}-sbt-$sbtBinaryV")
else Vector()
}
@deprecated("Use constructor with scalaVersion and scalaBinaryVersion", "1.5.0")
def makeCrossTarget(t: File, bv: String, sbtv: String, plugin: Boolean, cross: Boolean): File = {
val scalaBase = if (cross) t / ("scala-" + bv) else t
if (plugin) scalaBase / ("sbt-" + sbtv) else scalaBase
}
def makeCrossTarget(
t: File,
scalaVersion: String,
scalaBinaryVersion: String,
sbtv: String,
plugin: Boolean,
cross: Boolean
): File = {
val scalaSuffix =
if (ScalaArtifacts.isScala3(scalaVersion)) scalaVersion else scalaBinaryVersion
val scalaBase = if (cross) t / ("scala-" + scalaSuffix) else t
if (plugin) scalaBase / ("sbt-" + sbtv) else scalaBase
}
private def fetchBridgeBinaryJarTask(scalaVersion: String): Initialize[Task[Option[File]]] =
Def.task {
val bridgeJar = ZincLmUtil.fetchDefaultBridgeModule(
scalaVersion,
dependencyResolution.value,
updateConfiguration.value,
(update / unresolvedWarningConfiguration).value,
streams.value.log
)
Some(bridgeJar)
}
def compilersSetting = {
compilers := {
val st = state.value
val g = BuildPaths.getGlobalBase(st)
val zincDir = BuildPaths.getZincDirectory(st, g)
val app = appConfiguration.value
val launcher = app.provider.scalaProvider.launcher
val dr = scalaCompilerBridgeDependencyResolution.value
val scalac =
scalaCompilerBridgeBinaryJar.value match {
case Some(jar) =>
AlternativeZincUtil.scalaCompiler(
scalaInstance = scalaInstance.value,
classpathOptions = classpathOptions.value,
compilerBridgeJar = jar,
classLoaderCache = st.get(BasicKeys.classLoaderCache)
)
case _ =>
ZincLmUtil.scalaCompiler(
scalaInstance = scalaInstance.value,
classpathOptions = classpathOptions.value,
globalLock = launcher.globalLock,
componentProvider = app.provider.components,
secondaryCacheDir = Option(zincDir),
dependencyResolution = dr,
compilerBridgeSource = scalaCompilerBridgeSource.value,
scalaJarsTarget = zincDir,
classLoaderCache = st.get(BasicKeys.classLoaderCache),
log = streams.value.log
)
}
val compilers = ZincUtil.compilers(
instance = scalaInstance.value,
classpathOptions = classpathOptions.value,
javaHome = javaHome.value.map(_.toPath),
scalac
)
val classLoaderCache = state.value.classLoaderCache
if (java.lang.Boolean.getBoolean("sbt.disable.interface.classloader.cache")) compilers
else {
compilers.withScalac(
compilers.scalac match {
case x: AnalyzingCompiler => x.withClassLoaderCache(classLoaderCache)
case x => x
}
)
}
}
}
def defaultCompileSettings: Seq[Setting[_]] =
globalDefaults(
Seq(
enableBinaryCompileAnalysis :== true,
enableConsistentCompileAnalysis :== SysProp.analysis2024,
)
)
lazy val configTasks: Seq[Setting[_]] = docTaskSettings(doc) ++
inTask(compile)(compileInputsSettings) ++
inTask(compileJava)(
Seq(
compileInputs := {
val opts = (compileJava / compileOptions).value
(compile / compileInputs).value.withOptions(opts)
},
compileOptions := {
val opts = (compile / compileOptions).value
val cp0 = dependencyVirtualClasspath.value
val cp = backendOutput.value +: data(cp0)
opts.withClasspath(cp.toArray)
}
)
) ++
configGlobal ++ defaultCompileSettings ++ compileAnalysisSettings ++ Seq(
compileOutputs := {
import scala.collection.JavaConverters._
val c = fileConverter.value
val classFiles =
manipulateBytecode.value.analysis.readStamps.getAllProductStamps.keySet.asScala
(classFiles.toSeq map { x =>
c.toPath(x)
}) :+ compileAnalysisFile.value.toPath
},
compileOutputs := compileOutputs.triggeredBy(compile).value,
tastyFiles := Def.taskIf {
if (ScalaArtifacts.isScala3(scalaVersion.value)) {
val _ = compile.value
val tastyFiles = classDirectory.value.**("*.tasty").get
tastyFiles.map(_.getAbsoluteFile)
} else Nil
}.value,
clean := (compileOutputs / clean).value,
earlyOutputPing := Def.promise[Boolean],
compileProgress := {
val s = streams.value
val promise = earlyOutputPing.value
val mn = moduleName.value
val c = configuration.value
new CompileProgress {
override def afterEarlyOutput(isSuccess: Boolean): Unit = {
if (isSuccess) s.log.debug(s"[$mn / $c] early output is success")
else s.log.debug(s"[$mn / $c] early output can't be made because of macros")
promise.complete(Value(isSuccess))
}
}
},
compileEarly := compileEarlyTask.value,
compile := compileTask.value,
compileScalaBackend := compileScalaBackendTask.value,
compileJava := compileJavaTask.value,
compileSplit := {
// conditional task
if (incOptions.value.pipelining) compileJava.value
else compileScalaBackend.value
},
internalDependencyConfigurations := InternalDependencies.configurations.value,
manipulateBytecode := compileSplit.value,
compileIncremental := compileIncrementalTask.tag(Tags.Compile, Tags.CPU).value,
printWarnings := printWarningsTask.value,
compileAnalysisFilename := {
// Here, if the user wants cross-scala-versioning, we also append it
// to the analysis cache, so we keep the scala versions separated.
val binVersion = scalaBinaryVersion.value
val extra =
if (crossPaths.value) s"_$binVersion"
else ""
s"inc_compile$extra.zip"
},
earlyCompileAnalysisFile := {
earlyCompileAnalysisTargetRoot.value / compileAnalysisFilename.value
},
compileAnalysisFile := {
compileAnalysisTargetRoot.value / compileAnalysisFilename.value
},
externalHooks := IncOptions.defaultExternal,
zincCompilationListeners := Seq.empty,
incOptions := {
val old = incOptions.value
val extHooks = externalHooks.value
val newExtHooks = extHooks.withInvalidationProfiler(
() => new DefaultRunProfiler(zincCompilationListeners.value)
)
old
.withAuxiliaryClassFiles(auxiliaryClassFiles.value.toArray)
.withExternalHooks(newExtHooks)
.withClassfileManagerType(
Option(
TransactionalManagerType
.of( // https://github.com/sbt/sbt/issues/1673
crossTarget.value / s"${prefix(configuration.value.name)}classes.bak",
streams.value.log
): ClassFileManagerType
).toOptional
)
.withPipelining(usePipelining.value)
},
scalacOptions := {
val old = scalacOptions.value
val converter = fileConverter.value
if (exportPipelining.value)
Vector("-Ypickle-java", "-Ypickle-write", converter.toPath(earlyOutput.value).toString) ++ old
else old
},
scalacOptions := {
val old = scalacOptions.value
if (sbtPlugin.value && VersionNumber(scalaVersion.value)
.matchesSemVer(SemanticSelector("=2.12 >=2.12.13")))
old ++ Seq("-Wconf:cat=unused-nowarn:s", "-Xsource:3")
else old
},
persistJarClasspath :== true,
classpathEntryDefinesClassVF := {
(if (persistJarClasspath.value) classpathDefinesClassCache.value
else VirtualFileValueCache.definesClassCache(fileConverter.value)).get
},
compileIncSetup := compileIncSetupTask.value,
console := consoleTask.value,
collectAnalyses := Definition.collectAnalysesTask.map(_ => ()).value,
consoleQuick := consoleQuickTask.value,
discoveredMainClasses := (compile map discoverMainClasses storeAs discoveredMainClasses xtriggeredBy compile).value,
discoveredSbtPlugins := discoverSbtPluginNames.value,
// This fork options, scoped to the configuration is used for tests
forkOptions := forkOptionsTask.value,
selectMainClass := mainClass.value orElse askForMainClass(discoveredMainClasses.value),
run / mainClass := (run / selectMainClass).value,
mainClass := {
val logWarning = state.value.currentCommand.forall(!_.commandLine.split(" ").exists {
case "run" | "runMain" => true
case r =>
r.split("/") match {
case Array(parts @ _*) =>
parts.lastOption match {
case Some("run" | "runMain") => true
case _ => false
}
}
})
pickMainClassOrWarn(discoveredMainClasses.value, streams.value.log, logWarning)
},
runMain := foregroundRunMainTask.evaluated,
run := foregroundRunTask.evaluated,
fgRun := runTask(fullClasspath, (run / mainClass), (run / runner)).evaluated,
fgRunMain := runMainTask(fullClasspath, (run / runner)).evaluated,
copyResources := copyResourcesTask.value,
// note that we use the same runner and mainClass as plain run
mainBgRunMainTaskForConfig(This),
mainBgRunTaskForConfig(This)
) ++ inTask(run)(runnerSettings ++ newRunnerSettings)
private[this] lazy val configGlobal = globalDefaults(
Seq(
initialCommands :== "",
cleanupCommands :== "",
asciiGraphWidth :== 80
)
)
lazy val projectTasks: Seq[Setting[_]] = Seq(
cleanFiles := cleanFilesTask.value,
cleanKeepFiles := Vector.empty,
cleanKeepGlobs ++= historyPath.value.map(_.toGlob).toVector,
clean := Def.taskDyn(Clean.task(resolvedScoped.value.scope, full = true)).value,
consoleProject := consoleProjectTask.value,
transitiveDynamicInputs := WatchTransitiveDependencies.task.value,
) ++ sbt.internal.DeprecatedContinuous.taskDefinitions
def generate(generators: SettingKey[Seq[Task[Seq[File]]]]): Initialize[Task[Seq[File]]] =
generators { _.join.map(_.flatten) }
@deprecated(
"The watchTransitiveSourcesTask is used only for legacy builds and will be removed in a future version of sbt.",
"1.3.0"
)
def watchTransitiveSourcesTask: Initialize[Task[Seq[Source]]] =
watchTransitiveSourcesTaskImpl(watchSources)
private def watchTransitiveSourcesTaskImpl(
key: TaskKey[Seq[Source]]
): Initialize[Task[Seq[Source]]] = {
import ScopeFilter.Make.{ inDependencies => inDeps, _ }
val selectDeps = ScopeFilter(inAggregates(ThisProject) || inDeps(ThisProject))
val allWatched = (key ?? Nil).all(selectDeps)
Def.task { allWatched.value.flatten }
}
def transitiveUpdateTask: Initialize[Task[Seq[UpdateReport]]] = {
import ScopeFilter.Make.{ inDependencies => inDeps, _ }
val selectDeps = ScopeFilter(inDeps(ThisProject, includeRoot = false))
val allUpdates = update.?.all(selectDeps)
// If I am a "build" (a project inside project/) then I have a globalPluginUpdate.
Def.task { allUpdates.value.flatten ++ globalPluginUpdate.?.value }
}
@deprecated("This is no longer used to implement continuous execution", "1.3.0")
def watchSetting: Initialize[Watched] =
Def.setting {
val getService = watchService.value
val interval = pollInterval.value
val _antiEntropy = watchAntiEntropy.value
val base = thisProjectRef.value
val msg = watchingMessage.?.value.getOrElse(Watched.defaultWatchingMessage)
val trigMsg = triggeredMessage.?.value.getOrElse(Watched.defaultTriggeredMessage)
new Watched {
val scoped = (base / watchTransitiveSources)
val key = scoped.scopedKey
override def antiEntropy: FiniteDuration = _antiEntropy
override def pollInterval: FiniteDuration = interval
override def watchingMessage(s: WatchState) = msg(s)
override def triggeredMessage(s: WatchState) = trigMsg(s)
override def watchService() = getService()
override def watchSources(s: State) =
EvaluateTask(Project structure s, key, s, base) match {
case Some((_, Value(ps))) => ps
case Some((_, Inc(i))) => throw i
case None => sys.error("key not found: " + Def.displayFull(key))
}
}
}
def scalaInstanceTask: Initialize[Task[ScalaInstance]] = Def.taskDyn {
// if this logic changes, ensure that `unmanagedScalaInstanceOnly` and `update` are changed
// appropriately to avoid cycles
scalaHome.value match {
case Some(h) => scalaInstanceFromHome(h)
case None =>
val scalaProvider = appConfiguration.value.provider.scalaProvider
val version = scalaVersion.value
if (version == scalaProvider.version) // use the same class loader as the Scala classes used by sbt
Def.task {
val allJars = scalaProvider.jars
val libraryJars = allJars.filter(_.getName == "scala-library.jar")
allJars.filter(_.getName == "scala-compiler.jar") match {
case Array(compilerJar) if libraryJars.nonEmpty =>
makeScalaInstance(
version,
libraryJars,
allJars,
Seq.empty,
state.value,
scalaInstanceTopLoader.value
)
case _ => ScalaInstance(version, scalaProvider)
}
} else
scalaInstanceFromUpdate
}
}
// Returns the ScalaInstance only if it was not constructed via `update`
// This is necessary to prevent cycles between `update` and `scalaInstance`
private[sbt] def unmanagedScalaInstanceOnly: Initialize[Task[Option[ScalaInstance]]] =
Def.taskDyn {
if (scalaHome.value.isDefined) Def.task(Some(scalaInstance.value)) else Def.task(None)
}
private[this] def noToolConfiguration(autoInstance: Boolean): String = {
val pre = "Missing Scala tool configuration from the 'update' report. "
val post =
if (autoInstance)
"'scala-tool' is normally added automatically, so this may indicate a bug in sbt or you may be removing it from ivyConfigurations, for example."
else
"Explicitly define scalaInstance or scalaHome or include Scala dependencies in the 'scala-tool' configuration."
pre + post
}
def scalaInstanceFromUpdate: Initialize[Task[ScalaInstance]] = Def.task {
val sv = scalaVersion.value
val fullReport = update.value
// For Scala 3, update scala-library.jar in `scala-tool` and `scala-doc-tool` in case a newer version
// is present in the `compile` configuration. This is needed once forwards binary compatibility is dropped
// to avoid NoSuchMethod exceptions when expanding macros.
def updateLibraryToCompileConfiguration(report: ConfigurationReport) =
if (!ScalaArtifacts.isScala3(sv)) report
else
(for {
compileConf <- fullReport.configuration(Configurations.Compile)
compileLibMod <- compileConf.modules.find(_.module.name == ScalaArtifacts.LibraryID)
reportLibMod <- report.modules.find(_.module.name == ScalaArtifacts.LibraryID)
if VersionNumber(reportLibMod.module.revision)
.matchesSemVer(SemanticSelector(s"<${compileLibMod.module.revision}"))
} yield {
val newMods = report.modules
.filterNot(_.module.name == ScalaArtifacts.LibraryID) :+ compileLibMod
report.withModules(newMods)
}).getOrElse(report)
val toolReport = updateLibraryToCompileConfiguration(
fullReport
.configuration(Configurations.ScalaTool)
.getOrElse(sys.error(noToolConfiguration(managedScalaInstance.value)))
)
if (Classpaths.isScala213(sv)) {
for {
compileReport <- fullReport.configuration(Configurations.Compile)
libName <- ScalaArtifacts.Artifacts
} {
for (lib <- compileReport.modules.find(_.module.name == libName)) {
val libVer = lib.module.revision
val n = name.value
if (VersionNumber(sv).matchesSemVer(SemanticSelector(s"<$libVer")))
sys.error(
s"""expected `$n/scalaVersion` to be "$libVer" or later,
|but found "$sv"; upgrade scalaVersion to fix the build.
|
|to support backwards-only binary compatibility (SIP-51),
|the Scala 2.13 compiler cannot be older than $libName on the
|dependency classpath.
|see `$n/evicted` to know why $libName $libVer is getting pulled in.
|""".stripMargin
)
}
}
}
def file(id: String): File = {
val files = for {
m <- toolReport.modules if m.module.name.startsWith(id)
(art, file) <- m.artifacts if art.`type` == Artifact.DefaultType
} yield file
files.headOption getOrElse sys.error(s"Missing $id jar file")
}
val allCompilerJars = toolReport.modules.flatMap(_.artifacts.map(_._2))
val allDocJars =
fullReport
.configuration(Configurations.ScalaDocTool)
.map(updateLibraryToCompileConfiguration)
.toSeq
.flatMap(_.modules)
.flatMap(_.artifacts.map(_._2))
val libraryJars = ScalaArtifacts.libraryIds(sv).map(file)
makeScalaInstance(
sv,
libraryJars,
allCompilerJars,
allDocJars,
state.value,
scalaInstanceTopLoader.value,
)
}
def makeScalaInstance(
version: String,
libraryJars: Array[File],
allCompilerJars: Seq[File],
allDocJars: Seq[File],
state: State,
topLoader: ClassLoader,
): ScalaInstance = {
val classLoaderCache = state.extendedClassLoaderCache
val compilerJars = allCompilerJars.filterNot(libraryJars.contains).distinct.toArray
val docJars = allDocJars
.filterNot(jar => libraryJars.contains(jar) || compilerJars.contains(jar))
.distinct
.toArray
val allJars = libraryJars ++ compilerJars ++ docJars
val libraryLoader = classLoaderCache(libraryJars.toList, topLoader)
val compilerLoader = classLoaderCache(compilerJars.toList, libraryLoader)
val fullLoader =
if (docJars.isEmpty) compilerLoader
else classLoaderCache(docJars.distinct.toList, compilerLoader)
new ScalaInstance(
version = version,
loader = fullLoader,
loaderCompilerOnly = compilerLoader,
loaderLibraryOnly = libraryLoader,
libraryJars = libraryJars,
compilerJars = compilerJars,
allJars = allJars,
explicitActual = Some(version)
)
}
def scalaInstanceFromHome(dir: File): Initialize[Task[ScalaInstance]] = Def.task {
val dummy = ScalaInstance(dir)(state.value.classLoaderCache.apply)
Seq(dummy.loader, dummy.loaderLibraryOnly).foreach {
case a: AutoCloseable => a.close()
case _ =>
}
makeScalaInstance(
dummy.version,
dummy.libraryJars,
dummy.compilerJars,
dummy.allJars,
state.value,
scalaInstanceTopLoader.value,
)
}
private[this] def testDefaults =
Defaults.globalDefaults(
Seq(
testFrameworks :== sbt.TestFrameworks.All,
testListeners :== Nil,
testOptions :== Nil,
testResultLogger :== TestResultLogger.Default,
testOnly / testFilter :== (selectedFilter _)
)
)
lazy val testTasks
: Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions(
testQuick
) ++ testDefaults ++ Seq(
testLoader := ClassLoaders.testTask.value,
loadedTestFrameworks := {
val loader = testLoader.value
val log = streams.value.log
testFrameworks.value.flatMap(f => f.create(loader, log).map(x => (f, x)).toIterable).toMap
},
definedTests := detectTests.value,
definedTestNames := (definedTests map (_.map(_.name).distinct) storeAs definedTestNames triggeredBy compile).value,
testQuick / testFilter := testQuickFilter.value,
executeTests := (
Def.taskDyn {
allTestGroupsTask(
(test / streams).value,
loadedTestFrameworks.value,
testLoader.value,
(test / testGrouping).value,
(test / testExecution).value,
(test / fullClasspath).value,
testForkedParallel.value,
(test / javaOptions).value,
(classLoaderLayeringStrategy).value,
projectId = s"${thisProject.value.id} / ",
)
}
).value,
// ((streams in test, loadedTestFrameworks, testLoader, testGrouping in test, testExecution in test, fullClasspath in test, javaHome in test, testForkedParallel, javaOptions in test) flatMap allTestGroupsTask).value,
Test / test / testResultLogger :== TestResultLogger.SilentWhenNoTests, // https://github.com/sbt/sbt/issues/1185
test := {
val trl = (Test / test / testResultLogger).value
val taskName = Project.showContextKey(state.value).show(resolvedScoped.value)
try trl.run(streams.value.log, executeTests.value, taskName)
finally close(testLoader.value)
},
testOnly := {
try inputTests(testOnly).evaluated
finally close(testLoader.value)
},
testQuick := {
try inputTests(testQuick).evaluated
finally close(testLoader.value)
}
)
private def close(sbtLoader: ClassLoader): Unit = sbtLoader match {
case u: AutoCloseable => u.close()
case c: ClasspathFilter => c.close()
case _ =>
}
/**
* A scope whose task axis is set to Zero.
*/
lazy val TaskZero: Scope = ThisScope.copy(task = Zero)
lazy val TaskGlobal: Scope = TaskZero
/**
* A scope whose configuration axis is set to Zero.
*/
lazy val ConfigZero: Scope = ThisScope.copy(config = Zero)
lazy val ConfigGlobal: Scope = ConfigZero
def testTaskOptions(key: Scoped): Seq[Setting[_]] =
inTask(key)(
Seq(
testListeners := {
val stateLogLevel = state.value.get(Keys.logLevel.key).getOrElse(Level.Info)
TestLogger.make(
streams.value.log,
closeableTestLogger(
streamsManager.value,
(resolvedScoped.value.scope / test),
logBuffered.value
),
Keys.logLevel.?.value.getOrElse(stateLogLevel),
) +:
new TestStatusReporter(succeededFile((test / streams).value.cacheDirectory)) +:
(TaskZero / testListeners).value
},
testOptions := Tests.Listeners(testListeners.value) +: (TaskZero / testOptions).value,
testExecution := testExecutionTask(key).value
)
) ++ inScope(GlobalScope)(
Seq(
derive(testGrouping := singleTestGroupDefault.value)
)
)
private[this] def closeableTestLogger(manager: Streams, baseKey: Scoped, buffered: Boolean)(
tdef: TestDefinition
): TestLogger.PerTest = {
val scope = baseKey.scope
val extra = scope.extra match { case Select(x) => x; case _ => AttributeMap.empty }
val key = ScopedKey(scope.copy(extra = Select(testExtra(extra, tdef))), baseKey.key)
val s = manager(key)
new TestLogger.PerTest(s.log, () => s.close(), buffered)
}
def testExtra(extra: AttributeMap, tdef: TestDefinition): AttributeMap = {
val mod = tdef.fingerprint match {
case f: SubclassFingerprint => f.isModule
case f: AnnotatedFingerprint => f.isModule
case _ => false
}
extra.put(name.key, tdef.name).put(isModule, mod)
}
def singleTestGroup(key: Scoped): Initialize[Task[Seq[Tests.Group]]] =
inTask(key, singleTestGroupDefault)
def singleTestGroupDefault: Initialize[Task[Seq[Tests.Group]]] = Def.task {
val tests = definedTests.value
val fk = fork.value
val opts = forkOptions.value
Seq(
new Tests.Group(
"",
tests,
if (fk) Tests.SubProcess(opts) else Tests.InProcess,
Seq.empty
)
)
}
def forkOptionsTask: Initialize[Task[ForkOptions]] =
Def.task {
ForkOptions(
javaHome = javaHome.value,
outputStrategy = outputStrategy.value,
// bootJars is empty by default because only jars on the user's classpath should be on the boot classpath
bootJars = Vector(),
workingDirectory = Some(baseDirectory.value),
runJVMOptions = javaOptions.value.toVector,
connectInput = connectInput.value,
envVars = envVars.value
)
}
def testExecutionTask(task: Scoped): Initialize[Task[Tests.Execution]] =
Def.task {
new Tests.Execution(
(task / testOptions).value,
(task / parallelExecution).value,
(task / tags).value
)
}
def testQuickFilter: Initialize[Task[Seq[String] => Seq[String => Boolean]]] =
Def.task {
val cp = (test / fullClasspath).value
val s = (test / streams).value
val ans: Seq[Analysis] = cp.flatMap(_.metadata get Keys.analysis) map {
case a0: Analysis => a0
}
val succeeded = TestStatus.read(succeededFile(s.cacheDirectory))
val stamps = collection.mutable.Map.empty[String, Long]
def stamp(dep: String): Long = {
val stamps = for (a <- ans) yield intlStamp(dep, a, Set.empty)
if (stamps.isEmpty) Long.MinValue
else stamps.max
}
def intlStamp(c: String, analysis: Analysis, s: Set[String]): Long = {
if (s contains c) Long.MinValue
else
stamps.getOrElse(
c, {
val x = {
import analysis.{ apis, relations => rel }
rel.internalClassDeps(c).map(intlStamp(_, analysis, s + c)) ++
rel.externalDeps(c).map(stamp) ++
rel.productClassName.reverse(c).flatMap { pc =>
apis.internal.get(pc).map(_.compilationTimestamp)
} + Long.MinValue
}.max
if (x != Long.MinValue) {
stamps(c) = x
}
x
}
)
}
def noSuccessYet(test: String) = succeeded.get(test) match {
case None => true
case Some(ts) => stamps.synchronized(stamp(test)) > ts
}
args =>
for (filter <- selectedFilter(args))
yield (test: String) => filter(test) && noSuccessYet(test)
}
def succeededFile(dir: File) = dir / "succeeded_tests"
@nowarn
def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] =
inputTests0.mapReferenced(Def.mapScope(_ in key.key))
private[this] lazy val inputTests0: Initialize[InputTask[Unit]] = {
val parser = loadForParser(definedTestNames)((s, i) => testOnlyParser(s, i getOrElse Nil))
Def.inputTaskDyn {
val (selected, frameworkOptions) = parser.parsed
val s = streams.value
val filter = testFilter.value
val config = testExecution.value
implicit val display = Project.showContextKey(state.value)
val modifiedOpts = Tests.Filters(filter(selected)) +: Tests.Argument(frameworkOptions: _*) +: config.options
val newConfig = config.copy(options = modifiedOpts)
val output = allTestGroupsTask(
s,
loadedTestFrameworks.value,
testLoader.value,
testGrouping.value,
newConfig,
fullClasspath.value,
testForkedParallel.value,
javaOptions.value,
classLoaderLayeringStrategy.value,
projectId = s"${thisProject.value.id} / ",
)
val taskName = display.show(resolvedScoped.value)
val trl = testResultLogger.value
output.map(out => trl.run(s.log, out, taskName))
}
}
def createTestRunners(
frameworks: Map[TestFramework, Framework],
loader: ClassLoader,
config: Tests.Execution
): Map[TestFramework, Runner] = {
import Tests.Argument
val opts = config.options.toList
frameworks.map {
case (tf, f) =>
val args = opts.flatMap {
case Argument(None | Some(`tf`), args) => args
case _ => Nil
}
val mainRunner = f.runner(args.toArray, Array.empty[String], loader)
tf -> mainRunner
}
}
private[sbt] def allTestGroupsTask(
s: TaskStreams,
frameworks: Map[TestFramework, Framework],
loader: ClassLoader,
groups: Seq[Tests.Group],
config: Tests.Execution,
cp: Classpath,
): Initialize[Task[Tests.Output]] = {
allTestGroupsTask(
s,
frameworks,
loader,
groups,
config,
cp,
forkedParallelExecution = false,
javaOptions = Nil,
strategy = ClassLoaderLayeringStrategy.ScalaLibrary,
projectId = "",
)
}
private[sbt] def allTestGroupsTask(
s: TaskStreams,
frameworks: Map[TestFramework, Framework],
loader: ClassLoader,
groups: Seq[Tests.Group],
config: Tests.Execution,
cp: Classpath,
forkedParallelExecution: Boolean
): Initialize[Task[Tests.Output]] = {
allTestGroupsTask(
s,
frameworks,
loader,
groups,
config,
cp,
forkedParallelExecution,
javaOptions = Nil,
strategy = ClassLoaderLayeringStrategy.ScalaLibrary,
projectId = "",
)
}
private[sbt] def allTestGroupsTask(
s: TaskStreams,
frameworks: Map[TestFramework, Framework],
loader: ClassLoader,
groups: Seq[Tests.Group],
config: Tests.Execution,
cp: Classpath,
forkedParallelExecution: Boolean,
javaOptions: Seq[String],
strategy: ClassLoaderLayeringStrategy,
projectId: String
): Initialize[Task[Tests.Output]] = {
val processedOptions: Map[Tests.Group, Tests.ProcessedOptions] =
groups
.map(
group => group -> Tests.processOptions(config, group.tests.toVector, s.log)
)
.toMap
val testDefinitions: Iterable[TestDefinition] = processedOptions.values.flatMap(_.tests)
val filteredFrameworks: Map[TestFramework, Framework] = frameworks.filter {
case (_, framework) =>
TestFramework.getFingerprints(framework).exists { t =>
testDefinitions.exists { test =>
TestFramework.matches(t, test.fingerprint)
}
}
}
val runners = createTestRunners(filteredFrameworks, loader, config)
val groupTasks = groups map { group =>
group.runPolicy match {
case Tests.SubProcess(opts) =>
s.log.debug(s"javaOptions: ${opts.runJVMOptions}")
val forkedConfig = config.copy(parallel = config.parallel && forkedParallelExecution)
s.log.debug(s"Forking tests - parallelism = ${forkedConfig.parallel}")
ForkTests(
runners,
processedOptions(group),
forkedConfig,
cp.files,
opts,
s.log,
(Tags.ForkedTestGroup, 1) +: group.tags: _*
)
case Tests.InProcess =>
if (javaOptions.nonEmpty) {
s.log.warn("javaOptions will be ignored, fork is set to false")
}
Tests(
frameworks,
loader,
runners,
processedOptions(group),
config.copy(tags = config.tags ++ group.tags),
s.log
)
}
}
val output = Tests.foldTasks(groupTasks, config.parallel)
val result = output map { out =>
out.events.foreach {
case (suite, e) =>
if (strategy != ClassLoaderLayeringStrategy.Flat ||
strategy != ClassLoaderLayeringStrategy.ScalaLibrary) {
(e.throwables ++ e.throwables.flatMap(t => Option(t.getCause)))
.find { t =>
t.isInstanceOf[NoClassDefFoundError] ||
t.isInstanceOf[IllegalAccessError] ||
t.isInstanceOf[ClassNotFoundException]
}
.foreach { t =>
s.log.error(
s"Test suite $suite failed with $t.\nThis may be due to the "
+ s"ClassLoaderLayeringStrategy ($strategy) used by your task.\n"
+ "To improve performance and reduce memory, sbt attempts to cache the"
+ " class loaders used to load the project dependencies.\n"
+ "The project class files are loaded in a separate class loader that is"
+ " created for each test run.\nThe test class loader accesses the project"
+ " dependency classes using the cached project dependency classloader.\nWith"
+ " this approach, class loading may fail under the following conditions:\n\n"
+ " * Dependencies use reflection to access classes in your project's"
+ " classpath.\n Java serialization/deserialization may cause this.\n"
+ " * An open package is accessed across layers. If the project's classes"
+ " access or extend\n jvm package private classes defined in a"
+ " project dependency, it may cause an IllegalAccessError\n because the"
+ " jvm enforces package private at the classloader level.\n\n"
+ "These issues, along with others that were not enumerated above, may be"
+ " resolved by changing the class loader layering strategy.\n"
+ "The Flat and ScalaLibrary strategies bundle the full project classpath in"
+ " the same class loader.\nTo use one of these strategies, set the"
+ " ClassLoaderLayeringStrategy key\nin your configuration, for example:\n\n"
+ s"set ${projectId}Test / classLoaderLayeringStrategy :="
+ " ClassLoaderLayeringStrategy.ScalaLibrary\n"
+ s"set ${projectId}Test / classLoaderLayeringStrategy :="
+ " ClassLoaderLayeringStrategy.Flat\n\n"
+ "See ClassLoaderLayeringStrategy.scala for the full list of options."
)
}
}
}
val summaries =
runners map {
case (tf, r) =>
Tests.Summary(frameworks(tf).name, r.done())
}
out.copy(summaries = summaries)
}
Def.value { result }
}
def selectedFilter(args: Seq[String]): Seq[String => Boolean] = {
def matches(nfs: Seq[NameFilter], s: String) = nfs.exists(_.accept(s))
val (excludeArgs, includeArgs) = args.partition(_.startsWith("-"))
val includeFilters = includeArgs map GlobFilter.apply
val excludeFilters = excludeArgs.map(_.substring(1)).map(GlobFilter.apply)
(includeFilters, excludeArgs) match {
case (Nil, Nil) => Seq(const(true))
case (Nil, _) => Seq((s: String) => !matches(excludeFilters, s))
case _ =>
includeFilters.map(f => (s: String) => (f.accept(s) && !matches(excludeFilters, s)))
}
}
def detectTests: Initialize[Task[Seq[TestDefinition]]] =
Def.task {
Tests.discover(loadedTestFrameworks.value.values.toList, compile.value, streams.value.log)._1
}
def defaultRestrictions: Initialize[Seq[Tags.Rule]] =
Def.setting {
val par = parallelExecution.value
val max = EvaluateTask.SystemProcessors
Tags.limitAll(if (par) max else 1) ::
Tags.limit(Tags.ForkedTestGroup, 1) ::
Tags.exclusiveGroup(Tags.Clean) ::
Nil
}
lazy val packageBase: Seq[Setting[_]] = Seq(
artifact := Artifact(moduleName.value)
) ++ Defaults.globalDefaults(
Seq(
packageOptions :== Nil,
artifactName :== (Artifact.artifactName _)
)
)
lazy val packageConfig: Seq[Setting[_]] =
inTask(packageBin)(
Seq(
packageOptions := {
val n = name.value
val ver = version.value
val org = organization.value
val orgName = organizationName.value
val main = mainClass.value
val ts = packageTimestamp.value
val old = packageOptions.value
Package.addSpecManifestAttributes(n, ver, orgName) +:
Package.addImplManifestAttributes(n, ver, homepage.value, org, orgName) +:
Package.setFixedTimestamp(ts) +:
main.map(Package.MainClass.apply) ++: old
}
)
) ++
inTask(packageSrc)(
Seq(
packageOptions := {
val old = packageOptions.value
val ts = packageTimestamp.value
Package.addSpecManifestAttributes(
name.value,
version.value,
organizationName.value
) +: Package.setFixedTimestamp(ts) +: old
}
)
) ++
packageTaskSettings(packageBin, packageBinMappings) ++
packageTaskSettings(packageSrc, packageSrcMappings) ++
packageTaskSettings(packageDoc, packageDocMappings) ++
Seq(Keys.`package` := packageBin.value)
def packageBinMappings = products map { _ flatMap Path.allSubpaths }
def packageDocMappings = doc map { Path.allSubpaths(_).toSeq }
def packageSrcMappings = concatMappings(resourceMappings, sourceMappings)
private type Mappings = Initialize[Task[Seq[(File, String)]]]
def concatMappings(as: Mappings, bs: Mappings) =
(as zipWith bs)((a, b) => (a, b) map { case (a, b) => a ++ b })
// drop base directories, since there are no valid mappings for these
def sourceMappings: Initialize[Task[Seq[(File, String)]]] =
Def.task {
val sdirs = sourceDirectories.value
val base = baseDirectory.value
val relative = (f: File) => relativeTo(sdirs)(f).orElse(relativeTo(base)(f)).orElse(flat(f))
val exclude = Set(sdirs, base)
sources.value.flatMap {
case s if !exclude(s) => relative(s).map(s -> _)
case _ => None
}
}
def resourceMappings = relativeMappings(resources, resourceDirectories)
def relativeMappings(
files: Taskable[Seq[File]],
dirs: Taskable[Seq[File]]
): Initialize[Task[Seq[(File, String)]]] =
Def.task {
val rdirs = dirs.toTask.value.toSet
val relative = (f: File) => relativeTo(rdirs)(f).orElse(flat(f))
files.toTask.value.flatMap {
case r if !rdirs(r) => relative(r).map(r -> _)
case _ => None
}
}
def collectFiles(
dirs: Taskable[Seq[File]],
filter: Taskable[FileFilter],
excludes: Taskable[FileFilter]
): Initialize[Task[Seq[File]]] =
Def.task {
dirs.toTask.value.descendantsExcept(filter.toTask.value, excludes.toTask.value).get
}
def relativeMappings( // forward to widened variant
files: ScopedTaskable[Seq[File]],
dirs: ScopedTaskable[Seq[File]]
): Initialize[Task[Seq[(File, String)]]] = relativeMappings(files: Taskable[Seq[File]], dirs)
def collectFiles( // forward to widened variant
dirs: ScopedTaskable[Seq[File]],
filter: ScopedTaskable[FileFilter],
excludes: ScopedTaskable[FileFilter]
): Initialize[Task[Seq[File]]] = collectFiles(dirs: Taskable[Seq[File]], filter, excludes)
private[sbt] def configArtifactPathSetting(
art: SettingKey[Artifact],
extraPrefix: String
): Initialize[File] =
Def.setting {
crossTarget.value /
(prefix(configuration.value.name) + "early") / "early.jar"
}
private[sbt] def prefixArtifactPathSetting(
art: SettingKey[Artifact],
extraPrefix: String
): Initialize[File] =
Def.setting {
val f = artifactName.value
crossTarget.value / extraPrefix / f(
ScalaVersion(
(artifactName / scalaVersion).value,
(artifactName / scalaBinaryVersion).value
),
projectID.value,
art.value
)
}
def artifactPathSetting(art: SettingKey[Artifact]): Initialize[File] =
Def.setting {
val f = artifactName.value
crossTarget.value / f(
ScalaVersion(
(artifactName / scalaVersion).value,
(artifactName / scalaBinaryVersion).value
),
projectID.value,
art.value
)
}
def artifactSetting: Initialize[Artifact] =
Def.setting {
val a = artifact.value
val classifier = artifactClassifier.value
val cOpt = configuration.?.value
val cPart = cOpt flatMap {
case Compile => None
case Test => Some(Artifact.TestsClassifier)
case c => Some(c.name)
}
val combined = cPart.toList ++ classifier.toList
val configurations = cOpt.map(c => ConfigRef(c.name)).toVector
if (combined.isEmpty) a.withClassifier(None).withConfigurations(configurations)
else {
val a1 = a
.withClassifier(Some(combined.mkString("-")))
.withConfigurations(configurations)
// use "source" as opposed to "foo-source" to retrieve the type
classifier match {
case Some(c) => a1.withType(Artifact.classifierType(c))
case None => a1
}
}
}
@deprecated("The configuration(s) should not be decided based on the classifier.", "1.0.0")
def artifactConfigurations(
base: Artifact,
scope: Configuration,
classifier: Option[String]
): Iterable[Configuration] =
classifier match {
case Some(c) => Artifact.classifierConf(c) :: Nil
case None => scope :: Nil
}
def packageTaskSettings(key: TaskKey[File], mappingsTask: Initialize[Task[Seq[(File, String)]]]) =
inTask(key)(
Seq(
(TaskZero / key) := packageTask.value,
packageConfiguration := packageConfigurationTask.value,
mappings := mappingsTask.value,
packagedArtifact := (artifact.value -> key.value),
artifact := artifactSetting.value,
artifactPath := artifactPathSetting(artifact).value
)
)
def packageTask: Initialize[Task[File]] =
Def.task {
val config = packageConfiguration.value
val s = streams.value
Package(
config,
s.cacheStoreFactory,
s.log,
Package.timeFromConfiguration(config)
)
config.jar
}
def packageConfigurationTask: Initialize[Task[Package.Configuration]] =
Def.task {
new Package.Configuration(mappings.value, artifactPath.value, packageOptions.value)
}
def askForMainClass(classes: Seq[String]): Option[String] =
sbt.SelectMainClass(
if (classes.length >= 10) Some(SimpleReader(ITerminal.get).readLine(_))
else
Some(s => {
def print(st: String) = { scala.Console.out.print(st); scala.Console.out.flush() }
print(s)
ITerminal.get.withRawInput {
try ITerminal.get.inputStream.read match {
case -1 | -2 => None
case b =>
val res = b.toChar.toString
println(res)
Some(res)
} catch { case e: InterruptedException => None }
}
}),
classes
)
def pickMainClass(classes: Seq[String]): Option[String] =
sbt.SelectMainClass(None, classes)
private def pickMainClassOrWarn(
classes: Seq[String],
logger: Logger,
logWarning: Boolean
): Option[String] = {
classes match {
case multiple if multiple.size > 1 && logWarning =>
val msg =
"multiple main classes detected: run 'show discoveredMainClasses' to see the list"
logger.warn(msg)
case _ =>
}
pickMainClass(classes)
}
/** Implements `cleanFiles` task. */
private[sbt] def cleanFilesTask: Initialize[Task[Vector[File]]] = Def.task { Vector.empty[File] }
private[this] def termWrapper(canonical: Boolean, echo: Boolean): (() => Unit) => (() => Unit) =
(f: () => Unit) =>
() => {
val term = ITerminal.get
if (!canonical) {
term.enterRawMode()
if (echo) term.setEchoEnabled(echo)
} else if (!echo) term.setEchoEnabled(false)
try f()
finally {
if (!canonical) term.exitRawMode()
if (!echo) term.setEchoEnabled(true)
}
}
def bgRunMainTask(
products: Initialize[Task[Classpath]],
classpath: Initialize[Task[Classpath]],
copyClasspath: Initialize[Boolean],
scalaRun: Initialize[Task[ScalaRun]]
): Initialize[InputTask[JobHandle]] = {
val parser = Defaults.loadForParser(discoveredMainClasses)(
(s, names) => Defaults.runMainParser(s, names getOrElse Nil)
)
Def.inputTask {
val service = bgJobService.value
val (mainClass, args) = parser.parsed
val hashClasspath = (bgRunMain / bgHashClasspath).value
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
service.runInBackgroundWithLoader(resolvedScoped.value, state.value) { (logger, workingDir) =>
val files =
if (copyClasspath.value)
service.copyClasspath(products.value, classpath.value, workingDir, hashClasspath)
else classpath.value
val cp = data(files)
scalaRun.value match {
case r: Run =>
val loader = r.newLoader(cp)
(Some(loader), wrapper(() => r.runWithLoader(loader, cp, mainClass, args, logger).get))
case sr =>
(None, wrapper(() => sr.run(mainClass, cp, args, logger).get))
}
}
}
}
def bgRunTask(
products: Initialize[Task[Classpath]],
classpath: Initialize[Task[Classpath]],
mainClassTask: Initialize[Task[Option[String]]],
copyClasspath: Initialize[Boolean],
scalaRun: Initialize[Task[ScalaRun]]
): Initialize[InputTask[JobHandle]] = {
import Def.parserToInput
val parser = Def.spaceDelimited()
Def.inputTask {
val service = bgJobService.value
val mainClass = mainClassTask.value getOrElse sys.error("No main class detected.")
val hashClasspath = (bgRun / bgHashClasspath).value
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
service.runInBackgroundWithLoader(resolvedScoped.value, state.value) { (logger, workingDir) =>
val files =
if (copyClasspath.value)
service.copyClasspath(products.value, classpath.value, workingDir, hashClasspath)
else classpath.value
val cp = data(files)
val args = parser.parsed
scalaRun.value match {
case r: Run =>
val loader = r.newLoader(cp)
(Some(loader), wrapper(() => r.runWithLoader(loader, cp, mainClass, args, logger).get))
case sr =>
(None, wrapper(() => sr.run(mainClass, cp, args, logger).get))
}
}
}
}
// runMain calls bgRunMain in the background and waits for the result.
def foregroundRunMainTask: Initialize[InputTask[Unit]] =
Def.inputTask {
val handle = bgRunMain.evaluated
val service = bgJobService.value
service.waitForTry(handle).get
}
// run calls bgRun in the background and waits for the result.
def foregroundRunTask: Initialize[InputTask[Unit]] =
Def.inputTask {
val handle = bgRun.evaluated
val service = bgJobService.value
service.waitForTry(handle).get
}
def runMainTask(
classpath: Initialize[Task[Classpath]],
scalaRun: Initialize[Task[ScalaRun]]
): Initialize[InputTask[Unit]] = {
val parser =
loadForParser(discoveredMainClasses)((s, names) => runMainParser(s, names getOrElse Nil))
Def.inputTask {
val (mainClass, args) = parser.parsed
scalaRun.value.run(mainClass, data(classpath.value), args, streams.value.log).get
}
}
def runTask(
classpath: Initialize[Task[Classpath]],
mainClassTask: Initialize[Task[Option[String]]],
scalaRun: Initialize[Task[ScalaRun]]
): Initialize[InputTask[Unit]] = {
import Def.parserToInput
val parser = Def.spaceDelimited()
Def.inputTask {
val mainClass = mainClassTask.value getOrElse sys.error("No main class detected.")
scalaRun.value.run(mainClass, data(classpath.value), parser.parsed, streams.value.log).get
}
}
def runnerTask: Setting[Task[ScalaRun]] = runner := runnerInit.value
def runnerInit: Initialize[Task[ScalaRun]] = Def.task {
val tmp = taskTemporaryDirectory.value
val resolvedScope = resolvedScoped.value.scope
val si = scalaInstance.value
val s = streams.value
val opts = forkOptions.value
val options = javaOptions.value
val trap = trapExit.value
if (fork.value) {
s.log.debug(s"javaOptions: $options")
new ForkRun(opts)
} else {
if (options.nonEmpty) {
val mask = ScopeMask(project = false)
val showJavaOptions = Scope.displayMasked(
(resolvedScope / javaOptions).scopedKey.scope,
(resolvedScope / javaOptions).key.label,
mask
)
val showFork = Scope.displayMasked(
(resolvedScope / fork).scopedKey.scope,
(resolvedScope / fork).key.label,
mask
)
s.log.warn(s"$showJavaOptions will be ignored, $showFork is set to false")
}
new Run(si, trap, tmp)
}
}
private def foreachJobTask(
f: (BackgroundJobService, JobHandle) => Unit
): Initialize[InputTask[Unit]] = {
val parser: Initialize[State => Parser[Seq[JobHandle]]] = Def.setting { (s: State) =>
val extracted = Project.extract(s)
val service = extracted.get(bgJobService)
// you might be tempted to use the jobList task here, but the problem
// is that its result gets cached during execution and therefore stale
BackgroundJobService.jobIdParser(s, service.jobs)
}
Def.inputTask {
val handles = parser.parsed
for (handle <- handles) {
f(bgJobService.value, handle)
}
}
}
def psTask: Initialize[Task[Seq[JobHandle]]] =
Def.task {
val xs = bgList.value
val s = streams.value
xs foreach { x =>
s.log.info(x.toString)
}
xs
}
def bgStopTask: Initialize[InputTask[Unit]] = foreachJobTask { (manager, handle) =>
manager.stop(handle)
}
def bgWaitForTask: Initialize[InputTask[Unit]] = foreachJobTask { (manager, handle) =>
manager.waitForTry(handle)
()
}
def docTaskSettings(key: TaskKey[File] = doc): Seq[Setting[_]] =
inTask(key)(
Seq(
apiMappings ++= {
val dependencyCp = dependencyClasspath.value
val log = streams.value.log
if (autoAPIMappings.value) APIMappings.extract(dependencyCp, log).toMap
else Map.empty[File, URL]
},
fileInputOptions := Seq("-doc-root-content", "-diagrams-dot-path"),
scalacOptions := {
val compileOptions = scalacOptions.value
val sv = scalaVersion.value
val config = configuration.value
val projectName = name.value
if (ScalaArtifacts.isScala3(sv)) {
val project = if (config == Compile) projectName else s"$projectName-$config"
if (scalaVersion.value.startsWith("3.0.0")) {
Seq("-project", project)
} else {
compileOptions ++ Seq("-project", project)
}
} else compileOptions
},
(TaskZero / key) := {
val s = streams.value
val cs: Compilers = compilers.value
val srcs = sources.value
val out = target.value
val sOpts = scalacOptions.value
val xapis = apiMappings.value
val hasScala = srcs.exists(_.name.endsWith(".scala"))
val hasJava = srcs.exists(_.name.endsWith(".java"))
val cp = data(dependencyClasspath.value).toList
val label = nameForSrc(configuration.value.name)
val fiOpts = fileInputOptions.value
val reporter = (compile / bspReporter).value
val converter = fileConverter.value
val tFiles = tastyFiles.value
val sv = scalaVersion.value
val allDeps = allDependencies.value
(hasScala, hasJava) match {
case (true, _) =>
val options = sOpts ++ Opts.doc.externalAPI(xapis)
val runDoc = Doc.scaladoc(label, s.cacheStoreFactory sub "scala", cs.scalac match {
case ac: AnalyzingCompiler => ac.onArgs(exported(s, "scaladoc"))
}, fiOpts)
def isScala3Doc(module: ModuleID): Boolean = {
module.configurations.exists(_.startsWith(Configurations.ScalaDocTool.name)) &&
module.name == ScalaArtifacts.Scala3DocID
}
if (ScalaArtifacts.isScala3M123(sv) && !allDeps.exists(isScala3Doc)) {
Array(
"Unresolved scala3doc artifact",
"add 'ThisBuild / resolvers += Resolver.JCenterRepository'"
).foreach(m => s.log.error(m))
}
val docSrcs = if (ScalaArtifacts.isScala3(sv)) tFiles else srcs
runDoc(docSrcs, cp, out, options, maxErrors.value, s.log)
case (_, true) =>
val javadoc =
sbt.inc.Doc.cachedJavadoc(label, s.cacheStoreFactory sub "java", cs.javaTools)
javadoc.run(
srcs.toList map { x =>
converter.toVirtualFile(x.toPath)
},
cp map { x =>
converter.toVirtualFile(x.toPath)
},
converter,
out.toPath,
javacOptions.value.toList,
IncToolOptionsUtil.defaultIncToolOptions(),
s.log,
reporter
)
case _ => () // do nothing
}
out
}
)
)
def mainBgRunTask = mainBgRunTaskForConfig(Select(Runtime))
def mainBgRunMainTask = mainBgRunMainTaskForConfig(Select(Runtime))
private[this] def mainBgRunTaskForConfig(c: ScopeAxis[ConfigKey]) =
bgRun := bgRunTask(
exportedProductJars,
This / c / This / fullClasspathAsJars,
run / mainClass,
bgRun / bgCopyClasspath,
run / runner
).evaluated
private[this] def mainBgRunMainTaskForConfig(c: ScopeAxis[ConfigKey]) =
bgRunMain := bgRunMainTask(
exportedProductJars,
This / c / This / fullClasspathAsJars,
bgRunMain / bgCopyClasspath,
run / runner
).evaluated
def discoverMainClasses(analysis: CompileAnalysis): Seq[String] = analysis match {
case analysis: Analysis =>
analysis.infos.allInfos.values.map(_.getMainClasses).flatten.toSeq.sorted
}
def consoleProjectTask =
Def.task {
ConsoleProject(state.value, (consoleProject / initialCommands).value)(streams.value.log)
println()
}
def consoleTask: Initialize[Task[Unit]] = consoleTask(fullClasspath, console)
def consoleQuickTask = consoleTask(externalDependencyClasspath, consoleQuick)
def consoleTask(classpath: TaskKey[Classpath], task: TaskKey[_]): Initialize[Task[Unit]] =
Def.task {
val si = (task / scalaInstance).value
val s = streams.value
val cpFiles = data((task / classpath).value)
val fullcp = (cpFiles ++ si.allJars).distinct
val tempDir = IO.createUniqueDirectory((task / taskTemporaryDirectory).value).toPath
val loader = ClasspathUtil.makeLoader(fullcp.map(_.toPath), si, tempDir)
val compiler =
(task / compilers).value.scalac match {
case ac: AnalyzingCompiler => ac.onArgs(exported(s, "scala"))
}
val sc = (task / scalacOptions).value
val ic = (task / initialCommands).value
val cc = (task / cleanupCommands).value
(new Console(compiler))(cpFiles, sc, loader, ic, cc)()(s.log).get
println()
}
private[this] def exported(w: PrintWriter, command: String): Seq[String] => Unit =
args => w.println((command +: args).mkString(" "))
private[this] def exported(s: TaskStreams, command: String): Seq[String] => Unit = {
val w = s.text(ExportStream)
try exported(w, command)
finally w.close() // workaround for #937
}
/** Handles traditional Scalac compilation. For non-pipelined compilation,
* this also handles Java compilation.
*/
private[sbt] def compileScalaBackendTask: Initialize[Task[CompileResult]] = Def.task {
val setup: Setup = compileIncSetup.value
val analysisResult: CompileResult = compileIncremental.value
val exportP = exportPipelining.value
// Save analysis midway if pipelining is enabled
if (analysisResult.hasModified && exportP) {
val store = AnalysisUtil.staticCachedStore(
analysisFile = setup.cacheFile.toPath,
useTextAnalysis = !enableBinaryCompileAnalysis.value,
useConsistent = enableConsistentCompileAnalysis.value,
)
val contents = AnalysisContents.create(analysisResult.analysis(), analysisResult.setup())
store.set(contents)
// this stores the eary analysis (again) in case the subproject contains a macro
setup.earlyAnalysisStore.toOption map { earlyStore =>
earlyStore.set(contents)
}
}
analysisResult
}
/** Block on earlyOutputPing promise, which will be completed by `compile` midway
* via `compileProgress` implementation.
*/
private[sbt] def compileEarlyTask: Initialize[Task[CompileAnalysis]] = Def.task {
if ({
streams.value.log
.debug(s"${name.value}: compileEarly: blocking on earlyOutputPing")
earlyOutputPing.await.value
}) {
val store = AnalysisUtil.staticCachedStore(
analysisFile = earlyCompileAnalysisFile.value.toPath,
useTextAnalysis = !enableBinaryCompileAnalysis.value,
useConsistent = enableConsistentCompileAnalysis.value,
)
store.get.toOption match {
case Some(contents) => contents.getAnalysis
case _ => Analysis.empty
}
} else {
compile.value
}
}
def compileTask: Initialize[Task[CompileAnalysis]] = Def.task {
val setup: Setup = compileIncSetup.value
val c = fileConverter.value
// TODO - expose bytecode manipulation phase.
val analysisResult: CompileResult = manipulateBytecode.value
if (analysisResult.hasModified) {
val store = AnalysisUtil.staticCachedStore(
analysisFile = setup.cacheFile.toPath,
useTextAnalysis = !enableBinaryCompileAnalysis.value,
useConsistent = enableConsistentCompileAnalysis.value,
)
val contents = AnalysisContents.create(analysisResult.analysis(), analysisResult.setup())
store.set(contents)
}
val map = managedFileStampCache.value
val analysis = analysisResult.analysis
import scala.collection.JavaConverters._
analysis.readStamps.getAllProductStamps.asScala.foreach {
case (f: VirtualFileRef, s) =>
map.put(c.toPath(f), sbt.nio.FileStamp.fromZincStamp(s))
}
analysis
}
def compileIncrementalTask = Def.task {
val s = streams.value
val ci = (compile / compileInputs).value
val ping = earlyOutputPing.value
val reporter = (compile / bspReporter).value
BspCompileTask
.compute(bspTargetIdentifier.value, thisProjectRef.value, configuration.value, ci) { task =>
// TODO - Should readAnalysis + saveAnalysis be scoped by the compile task too?
compileIncrementalTaskImpl(task, s, ci, ping, reporter)
}
}
private val incCompiler = ZincUtil.defaultIncrementalCompiler
private[sbt] def compileJavaTask: Initialize[Task[CompileResult]] = Def.task {
val s = streams.value
val r = compileScalaBackend.value
val in0 = (compileJava / compileInputs).value
val in = in0.withPreviousResult(PreviousResult.of(r.analysis, r.setup))
val reporter = (compile / bspReporter).value
try {
if (r.hasModified) {
val result0 = incCompiler
.asInstanceOf[sbt.internal.inc.IncrementalCompilerImpl]
.compileAllJava(in, s.log)
reporter.sendSuccessReport(result0.analysis())
result0.withHasModified(result0.hasModified || r.hasModified)
} else r
} catch {
case NonFatal(e) =>
reporter.sendFailureReport(in.options.sources)
throw e
}
}
private[this] def compileIncrementalTaskImpl(
task: BspCompileTask,
s: TaskStreams,
ci: Inputs,
promise: PromiseWrap[Boolean],
reporter: BuildServerReporter,
): CompileResult = {
lazy val x = s.text(ExportStream)
def onArgs(cs: Compilers) = {
cs.withScalac(
cs.scalac match {
case ac: AnalyzingCompiler => ac.onArgs(exported(x, "scalac"))
case x => x
}
)
}
def onProgress(s: Setup) = {
val cp = new BspCompileProgress(task, s.progress.asScala)
s.withProgress(cp)
}
val compilers: Compilers = ci.compilers
val setup: Setup = ci.setup
val i = ci
.withCompilers(onArgs(compilers))
.withSetup(onProgress(setup))
try {
val result = incCompiler.compile(i, s.log)
reporter.sendSuccessReport(result.getAnalysis)
result
} catch {
case e: Throwable =>
if (!promise.isCompleted) {
promise.failure(e)
ConcurrentRestrictions.cancelAllSentinels()
}
reporter.sendFailureReport(ci.options.sources)
throw e
} finally {
x.close() // workaround for #937
}
}
def compileIncSetupTask = Def.task {
val cp = dependencyPicklePath.value
val lookup = new PerClasspathEntryLookup {
private val cachedAnalysisMap: VirtualFile => Option[CompileAnalysis] =
analysisMap(cp)
private val cachedPerEntryDefinesClassLookup: VirtualFile => DefinesClass =
Keys.classpathEntryDefinesClassVF.value
override def analysis(classpathEntry: VirtualFile): Optional[CompileAnalysis] =
cachedAnalysisMap(classpathEntry).toOptional
override def definesClass(classpathEntry: VirtualFile): DefinesClass =
cachedPerEntryDefinesClassLookup(classpathEntry)
}
val extra = extraIncOptions.value.map(t2)
val eapath = earlyCompileAnalysisFile.value.toPath
val eaOpt =
if (exportPipelining.value) {
val store = AnalysisUtil.staticCachedStore(
analysisFile = eapath,
useTextAnalysis = !enableBinaryCompileAnalysis.value,
useConsistent = enableConsistentCompileAnalysis.value,
)
Some(store)
} else None
Setup.of(
lookup,
(compile / skip).value,
compileAnalysisFile.value.toPath,
compilerCache.value,
incOptions.value,
(compile / bspReporter).value,
Some((compile / compileProgress).value).toOptional,
eaOpt.toOptional,
extra.toArray,
)
}
def compileInputsSettings: Seq[Setting[_]] =
compileInputsSettings(dependencyPicklePath)
def compileInputsSettings(classpathTask: TaskKey[VirtualClasspath]): Seq[Setting[_]] = {
Seq(
compileOptions := {
val c = fileConverter.value
val cp0 = classpathTask.value
val cp = backendOutput.value +: data(cp0)
val vs = sources.value.toVector map { x =>
c.toVirtualFile(x.toPath)
}
val eo = CompileOutput(c.toPath(earlyOutput.value))
val eoOpt =
if (exportPipelining.value) Some(eo)
else None
CompileOptions.of(
cp.toArray,
vs.toArray,
c.toPath(backendOutput.value),
scalacOptions.value.toArray,
javacOptions.value.toArray,
maxErrors.value,
f1(
foldMappers(sourcePositionMappers.value, reportAbsolutePath.value, fileConverter.value)
),
compileOrder.value,
None.toOptional: Optional[NioPath],
Some(fileConverter.value).toOptional,
Some(reusableStamper.value).toOptional,
eoOpt.toOptional,
)
},
compilerReporter := {
new ManagedLoggedReporter(
maxErrors.value,
streams.value.log,
foldMappers(sourcePositionMappers.value, reportAbsolutePath.value, fileConverter.value)
)
},
compileInputs := {
val options = compileOptions.value
val setup = compileIncSetup.value
val prev = previousCompile.value
Inputs.of(
compilers.value,
options,
setup,
prev
)
}
)
}
private[sbt] def foldMappers(
mappers: Seq[Position => Option[Position]],
reportAbsolutePath: Boolean,
fc: FileConverter
) = {
def withAbsoluteSource(p: Position): Position =
if (reportAbsolutePath) toAbsoluteSource(fc)(p) else p
mappers.foldRight({ p: Position =>
withAbsoluteSource(p) // Fallback if sourcePositionMappers is empty
}) {
(mapper, previousPosition) =>
{ p: Position =>
// To each mapper we pass the position with the absolute source (only if reportAbsolutePath = true of course)
mapper(withAbsoluteSource(p)).getOrElse(previousPosition(p))
}
}
}
private[sbt] def none[A]: Option[A] = (None: Option[A])
private[sbt] def jnone[A]: Optional[A] = none[A].toOptional
def compileAnalysisSettings: Seq[Setting[_]] = Seq(
previousCompile := {
val setup = compileIncSetup.value
val store = AnalysisUtil.staticCachedStore(
analysisFile = setup.cacheFile.toPath,
useTextAnalysis = !enableBinaryCompileAnalysis.value,
useConsistent = enableConsistentCompileAnalysis.value,
)
val prev = store.get().toOption match {
case Some(contents) =>
val analysis = Option(contents.getAnalysis).toOptional
val setup = Option(contents.getMiniSetup).toOptional
PreviousResult.of(analysis, setup)
case None => PreviousResult.of(jnone[CompileAnalysis], jnone[MiniSetup])
}
prev
}
)
def printWarningsTask: Initialize[Task[Unit]] =
Def.task {
val analysis = compile.value match { case a: Analysis => a }
val max = maxErrors.value
val spms = sourcePositionMappers.value
val problems =
analysis.infos.allInfos.values
.flatMap(i => i.getReportedProblems ++ i.getUnreportedProblems)
val reporter = new ManagedLoggedReporter(
max,
streams.value.log,
foldMappers(spms, reportAbsolutePath.value, fileConverter.value)
)
problems.foreach(p => reporter.log(p))
}
def sbtPluginExtra(m: ModuleID, sbtV: String, scalaV: String): ModuleID =
partialVersion(sbtV) match {
case Some((0, _)) | Some((1, _)) =>
m.extra(
PomExtraDependencyAttributes.SbtVersionKey -> sbtV,
PomExtraDependencyAttributes.ScalaVersionKey -> scalaV
)
.withCrossVersion(Disabled())
case Some(_) =>
// this produces a normal suffix like _sjs1_2.13
val prefix = s"sbt${binarySbtVersion(sbtV)}_"
m.cross(CrossVersion.binaryWith(prefix, ""))
case None =>
sys.error(s"unknown sbt version $sbtV")
}
def discoverSbtPluginNames: Initialize[Task[PluginDiscovery.DiscoveredNames]] = Def.taskDyn {
if (sbtPlugin.value) Def.task(PluginDiscovery.discoverSourceAll(compile.value))
else Def.task(PluginDiscovery.emptyDiscoveredNames)
}
def copyResourcesTask =
Def.task {
val t = classDirectory.value
val dirs = resourceDirectories.value.toSet
val s = streams.value
val syncDir = crossTarget.value / (prefix(configuration.value.name) + "sync")
val factory = CacheStoreFactory(syncDir)
val cacheStore = factory.make("copy-resource")
val converter = fileConverter.value
val flt: File => Option[File] = flat(t)
val transform: File => Option[File] =
(f: File) => rebase(resourceDirectories.value.sorted, t)(f).orElse(flt(f))
val mappings: Seq[(File, File)] = resources.value.flatMap {
case r if !dirs(r) => transform(r).map(r -> _)
case _ => None
}
s.log.debug("Copy resource mappings: " + mappings.mkString("\n\t", "\n\t", ""))
Sync.sync(cacheStore, fileConverter = converter)(mappings)
mappings
}
def runMainParser: (State, Seq[String]) => Parser[(String, Seq[String])] = {
import DefaultParsers._
(state, mainClasses) =>
Space ~> token(NotSpace examples mainClasses.toSet) ~ spaceDelimited("")
}
def testOnlyParser: (State, Seq[String]) => Parser[(Seq[String], Seq[String])] = {
(state, tests) =>
import DefaultParsers._
val selectTests = distinctParser(tests.toSet, true)
val options = (token(Space) ~> token("--") ~> spaceDelimited("