All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sbt.Defaults.scala Maven / Gradle / Ivy

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("




© 2015 - 2024 Weber Informatics LLC | Privacy Policy