diff --git a/build.sbt b/build.sbt index dcf98645b..98d06215a 100644 --- a/build.sbt +++ b/build.sbt @@ -181,7 +181,7 @@ def mimaSettingsSince(versions: Seq[String]): Seq[Def.Setting[_]] = Def settings ), ) -val scriptedSbtReduxMimaSettings = Def.settings(mimaPreviousArtifacts := Set()) +val scriptedSbtMimaSettings = Def.settings(mimaPreviousArtifacts := Set()) lazy val sbtRoot: Project = (project in file(".")) .aggregate(allProjects.map(p => LocalProject(p.id)): _*) @@ -618,43 +618,17 @@ val sbtProjDepsCompileScopeFilter = inConfigurations(Compile) ) -lazy val scriptedSbtReduxProj = (project in file("scripted-sbt-redux")) +lazy val scriptedSbtProj = (project in file("scripted-sbt")) .dependsOn(sbtProj % "compile;test->test", commandProj, utilLogging, utilScripted) .settings( baseSettings, - name := "Scripted sbt Redux", + name := "scripted-sbt", libraryDependencies ++= Seq(launcherInterface % "provided"), mimaSettings, - scriptedSbtReduxMimaSettings, + scriptedSbtMimaSettings, ) .configure(addSbtIO, addSbtCompilerInterface, addSbtLmCore) -lazy val scriptedSbtOldProj = (project in file("scripted-sbt-old")) - .dependsOn(scriptedSbtReduxProj) - .settings( - baseSettings, - name := "Scripted sbt", - mimaSettings, - mimaBinaryIssueFilters ++= Seq( - // sbt.test package is renamed to sbt.scriptedtest. - exclude[MissingClassProblem]("sbt.test.*"), - exclude[DirectMissingMethodProblem]("sbt.test.*"), - exclude[IncompatibleMethTypeProblem]("sbt.test.*"), - exclude[IncompatibleSignatureProblem]("sbt.test.*"), - ), - ) - -lazy val scriptedPluginProj = (project in file("scripted-plugin")) - .settings( - baseSettings, - name := "Scripted Plugin", - mimaSettings, - mimaBinaryIssueFilters ++= Seq( - // scripted plugin has moved into sbt mothership. - exclude[MissingClassProblem]("sbt.ScriptedPlugin*") - ), - ) - lazy val dependencyTreeProj = (project in file("dependency-tree")) .dependsOn(sbtProj) .settings( @@ -947,7 +921,6 @@ lazy val mainProj = (project in file("main")) runProj, commandProj, collectionProj, - scriptedPluginProj, zincLmIntegrationProj, utilLogging, ) @@ -1028,7 +1001,7 @@ lazy val sbtProj = (project in file("sbt-app")) // addSbtCompilerBridge lazy val serverTestProj = (project in file("server-test")) - .dependsOn(sbtProj % "compile->test", scriptedSbtReduxProj % "compile->test") + .dependsOn(sbtProj % "compile->test", scriptedSbtProj % "compile->test") .settings( testedBaseSettings, publish / skip := true, @@ -1261,7 +1234,7 @@ def scriptedTask(launch: Boolean): Def.Initialize[InputTask[Unit]] = Def.inputTa val _ = publishLocalBinAll.value val launchJar = s"-Dsbt.launch.jar=${(bundledLauncherProj / Compile / packageBin).value}" Scripted.doScripted( - (scriptedSbtReduxProj / scalaInstance).value, + (scriptedSbtProj / scalaInstance).value, scriptedSource.value, scriptedBufferLog.value, Def.setting(Scripted.scriptedParser(scriptedSource.value)).parsed, @@ -1269,7 +1242,7 @@ def scriptedTask(launch: Boolean): Def.Initialize[InputTask[Unit]] = Def.inputTa scriptedLaunchOpts.value ++ (if (launch) Some(launchJar) else None), scalaVersion.value, version.value, - (scriptedSbtReduxProj / Test / fullClasspathAsJars).value + (scriptedSbtProj / Test / fullClasspathAsJars).value .map(_.data) .filterNot(_.getName.contains("scala-compiler")), (bundledLauncherProj / Compile / packageBin).value, @@ -1288,9 +1261,7 @@ def allProjects = taskProj, stdTaskProj, runProj, - scriptedSbtReduxProj, - scriptedSbtOldProj, - scriptedPluginProj, + scriptedSbtProj, dependencyTreeProj, protocolProj, actionsProj, @@ -1364,9 +1335,7 @@ lazy val docProjects: ScopeFilter = ScopeFilter( inAnyProject -- inProjects( sbtRoot, sbtProj, - scriptedSbtReduxProj, - scriptedSbtOldProj, - scriptedPluginProj, + scriptedSbtProj, upperModules, lowerUtils, ), diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala index 3b5b062b6..d39f93077 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala @@ -347,10 +347,11 @@ trait Cont: $input, codeContentHash = Digest.dummy($codeContentHash), extraHash = Digest.dummy($extraHash), - tags = $tagsExpr + tags = $tagsExpr, + config = $cacheConfigExpr, )({ _ => $block - })($cacheConfigExpr) + }) } // This will generate following code for Def.declareOutput(...): diff --git a/main-actions/src/main/scala/sbt/Pkg.scala b/main-actions/src/main/scala/sbt/Pkg.scala index c98c82590..0c4146abc 100644 --- a/main-actions/src/main/scala/sbt/Pkg.scala +++ b/main-actions/src/main/scala/sbt/Pkg.scala @@ -104,7 +104,21 @@ object Pkg: val sources: Seq[(HashedVirtualFileRef, String)], val jar: VirtualFileRef, val options: Seq[PackageOption] - ) + ) { + import sbt.util.CacheImplicits.hashedVirtualFileRefToStr + private def sourcesStr: String = + sources + .map { case (k, v) => + s"${hashedVirtualFileRefToStr(k)}=$v" + } + .mkString(",\n ") + override def toString(): String = s"""Configuration( + sources = Seq(${sourcesStr}), + jar = ..., + options = ..., +) +""" + } object Configuration: given IsoLList.Aux[ diff --git a/main-actions/src/main/scala/sbt/Tests.scala b/main-actions/src/main/scala/sbt/Tests.scala index e4d227ee6..7b343989c 100644 --- a/main-actions/src/main/scala/sbt/Tests.scala +++ b/main-actions/src/main/scala/sbt/Tests.scala @@ -307,6 +307,7 @@ object Tests { in.filter(t => seen.add(f(t))) } + // Called by Defaults def apply( frameworks: Map[TestFramework, Framework], testLoader: ClassLoader, @@ -340,7 +341,7 @@ object Tests { apply(frameworks, testLoader, runners, o, config, log) } - def testTask( + private[sbt] def testTask( loader: ClassLoader, frameworks: Map[TestFramework, Framework], runners: Map[TestFramework, Runner], diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 6b8c31744..27548ab3e 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -101,7 +101,6 @@ import sbt.SlashSyntax0._ import sbt.internal.inc.{ Analysis, AnalyzingCompiler, - FileAnalysisStore, ManagedLoggedReporter, MixedAnalyzingCompiler, ScalaInstance @@ -140,6 +139,7 @@ import xsbti.compile.{ TastyFiles, TransactionalManagerType } +import sbt.internal.IncrementalTest object Defaults extends BuildCommon { final val CacheDirectoryName = "cache" @@ -153,18 +153,6 @@ object Defaults extends BuildCommon { def lock(app: xsbti.AppConfiguration): xsbti.GlobalLock = LibraryManagement.lock(app) - private[sbt] def extractAnalysis( - metadata: StringAttributeMap, - converter: FileConverter - ): Option[CompileAnalysis] = - def asBinary(file: File) = FileAnalysisStore.binary(file).get.asScala - def asText(file: File) = FileAnalysisStore.text(file).get.asScala - for - ref <- metadata.get(Keys.analysis) - file = converter.toPath(VirtualFileRef.of(ref)).toFile - content <- asBinary(file).orElse(asText(file)) - yield content.getAnalysis - private[sbt] def globalDefaults(ss: Seq[Setting[_]]): Seq[Setting[_]] = Def.defaultSettings(inScope(GlobalScope)(ss)) @@ -664,7 +652,14 @@ object Defaults extends BuildCommon { PluginDiscovery.writeDescriptors(discoveredSbtPlugins.value, resourceManaged.value) }).taskValue, managedResources := generate(resourceGenerators).value, - resources := Classpaths.concat(managedResources, unmanagedResources).value + resources := Classpaths.concat(managedResources, unmanagedResources).value, + resourceDigests := { + val uifs = (unmanagedResources / inputFileStamps).value + val mifs = (managedResources / inputFileStamps).value + (uifs ++ mifs).sortBy(_._1.toString()).map { case (p, fileStamp) => + FileStamp.toDigest(p, fileStamp) + } + }, ) // This exists for binary compatibility and probably never should have been public. def addBaseSources: Seq[Def.Setting[Task[Seq[File]]]] = Nil @@ -1323,7 +1318,8 @@ object Defaults extends BuildCommon { testListeners :== Nil, testOptions :== Nil, testResultLogger :== TestResultLogger.Default, - testOnly / testFilter :== (selectedFilter _) + testOnly / testFilter :== (IncrementalTest.selectedFilter _), + extraTestDigests :== Nil, ) ) lazy val testTasks: Seq[Setting[_]] = @@ -1342,7 +1338,11 @@ object Defaults extends BuildCommon { .storeAs(definedTestNames) .triggeredBy(compile) .value, - testQuick / testFilter := testQuickFilter.value, + definedTestDigests := IncrementalTest.definedTestDigestTask + .triggeredBy(compile) + .value, + testQuick / testFilter := IncrementalTest.filterTask.value, + extraTestDigests ++= IncrementalTest.extraTestDigestsTask.value, executeTests := { import sbt.TupleSyntax.* ( @@ -1422,7 +1422,10 @@ object Defaults extends BuildCommon { ), Keys.logLevel.?.value.getOrElse(stateLogLevel), ) +: - new TestStatusReporter(succeededFile((test / streams).value.cacheDirectory)) +: + TestStatusReporter( + definedTestDigests.value, + Def.cacheConfiguration.value, + ) +: (TaskZero / testListeners).value }, testOptions := Tests.Listeners(testListeners.value) +: (TaskZero / testOptions).value, @@ -1491,46 +1494,6 @@ object Defaults extends BuildCommon { ) } - def testQuickFilter: Initialize[Task[Seq[String] => Seq[String => Boolean]]] = - Def.task { - val cp = (test / fullClasspath).value - val s = (test / streams).value - val converter = fileConverter.value - val analyses = cp - .flatMap(a => extractAnalysis(a.metadata, converter)) - .collect { case analysis: Analysis => analysis } - val succeeded = TestStatus.read(succeededFile(s.cacheDirectory)) - val stamps = collection.mutable.Map.empty[String, Long] - def stamp(dep: String): Option[Long] = - analyses.flatMap(internalStamp(dep, _, Set.empty)).maxOption - def internalStamp(c: String, analysis: Analysis, alreadySeen: Set[String]): Option[Long] = { - if (alreadySeen.contains(c)) None - else - def computeAndStoreStamp: Option[Long] = { - import analysis.{ apis, relations } - val internalDeps = relations - .internalClassDeps(c) - .flatMap(internalStamp(_, analysis, alreadySeen + c)) - val externalDeps = relations.externalDeps(c).flatMap(stamp) - val classStamps = relations.productClassName.reverse(c).flatMap { pc => - apis.internal.get(pc).map(_.compilationTimestamp) - } - val maxStamp = (internalDeps ++ externalDeps ++ classStamps).maxOption - maxStamp.foreach(maxStamp => stamps(c) = maxStamp) - maxStamp - } - stamps.get(c).orElse(computeAndStoreStamp) - } - def noSuccessYet(test: String) = succeeded.get(test) match { - case None => true - case Some(ts) => stamps.synchronized(stamp(test)).exists(_ > 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)) @@ -1747,21 +1710,6 @@ object Defaults extends BuildCommon { 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 @@ -2625,7 +2573,7 @@ object Defaults extends BuildCommon { val cachedAnalysisMap: Map[VirtualFile, CompileAnalysis] = ( for attributed <- cp - analysis <- extractAnalysis(attributed.metadata, converter) + analysis <- BuildDef.extractAnalysis(attributed.metadata, converter) yield (converter.toVirtualFile(attributed.data), analysis) ).toMap val cachedPerEntryDefinesClassLookup: VirtualFile => DefinesClass = @@ -2784,10 +2732,17 @@ object Defaults extends BuildCommon { } def sbtPluginExtra(m: ModuleID, sbtV: String, scalaV: String): ModuleID = - m.extra( - PomExtraDependencyAttributes.SbtVersionKey -> sbtV, - PomExtraDependencyAttributes.ScalaVersionKey -> scalaV - ).withCrossVersion(Disabled()) + 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.task { sbtPlugin.value }).flatMapTask { case p => @@ -3132,7 +3087,7 @@ object Classpaths { Defaults.globalDefaults( Seq( publishMavenStyle :== true, - sbtPluginPublishLegacyMavenStyle := true, + sbtPluginPublishLegacyMavenStyle :== true, publishArtifact :== true, (Test / publishArtifact) :== false ) @@ -3140,8 +3095,10 @@ object Classpaths { private lazy val publishSbtPluginMavenStyle = Def.task(sbtPlugin.value && publishMavenStyle.value) private lazy val packagedDefaultArtifacts = packaged(defaultArtifactTasks) - private lazy val emptyArtifacts = Def.task(Map.empty[Artifact, HashedVirtualFileRef]) - + private lazy val sbt2Plus: Def.Initialize[Boolean] = Def.setting { + val sbtV = (pluginCrossBuild / sbtBinaryVersion).value + sbtV != "1.0" && !sbtV.startsWith("0.") + } val jvmPublishSettings: Seq[Setting[_]] = Seq( artifacts := artifactDefs(defaultArtifactTasks).value, packagedArtifacts := Def @@ -3161,13 +3118,39 @@ object Classpaths { * valid POM file, that is a POM file that Maven can resolve. */ private def mavenArtifactsOfSbtPlugin: Def.Initialize[Task[Map[Artifact, HashedVirtualFileRef]]] = + Def.task { + // This is a conditional task. The top-level must be an if expression. + if (sbt2Plus.value) { + // Both POMs and JARs are Maven-compatible in sbt 2.x, so ignore the workarounds + packagedDefaultArtifacts.value + } else { + val crossVersion = sbtCrossVersion.value + val legacyPomArtifact = (makePom / artifact).value + val converter = fileConverter.value + def addSuffix(a: Artifact): Artifact = a.withName(crossVersion(a.name)) + Map( + addSuffix(legacyPomArtifact) -> converter.toVirtualFile( + makeMavenPomOfSbtPlugin.value.toPath() + ) + ) ++ + pomConsistentArtifactsForLegacySbt.value ++ + legacyPackagedArtifacts.value + } + } + + private def legacyPackagedArtifacts: Def.Initialize[Task[Map[Artifact, HashedVirtualFileRef]]] = + Def.task { + // This is a conditional task. The top-level must be an if expression. + if (sbtPluginPublishLegacyMavenStyle.value) packagedDefaultArtifacts.value + else Map.empty[Artifact, HashedVirtualFileRef] + } + + private def pomConsistentArtifactsForLegacySbt + : Def.Initialize[Task[Map[Artifact, HashedVirtualFileRef]]] = Def.task { val crossVersion = sbtCrossVersion.value - val legacyArtifact = (makePom / artifact).value - val converter = fileConverter.value - val pom = converter.toVirtualFile(makeMavenPomOfSbtPlugin.value.toPath) val legacyPackages = packaged(defaultPackages).value - def addSuffix(a: Artifact): Artifact = a.withName(crossVersion(a.name)) + val converter = fileConverter.value def copyArtifact( artifact: Artifact, fileRef: HashedVirtualFileRef @@ -3179,11 +3162,9 @@ object Classpaths { IO.copyFile(file, targetFile) artifact.withName(nameWithSuffix) -> converter.toVirtualFile(targetFile.toPath) } - val packages = legacyPackages.map { case (artifact, file) => copyArtifact(artifact, file) } - val legacyPackagedArtifacts = Def - .ifS(sbtPluginPublishLegacyMavenStyle.toTask)(packagedDefaultArtifacts)(emptyArtifacts) - .value - packages + (addSuffix(legacyArtifact) -> pom) ++ legacyPackagedArtifacts + legacyPackages.map { case (artifact, file) => + copyArtifact(artifact, file); + } } private def sbtCrossVersion: Def.Initialize[String => String] = Def.setting { diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index a04d545b3..5a8740f14 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -37,7 +37,7 @@ import sbt.librarymanagement._ import sbt.librarymanagement.ivy.{ Credentials, IvyConfiguration, IvyPaths, UpdateOptions } import sbt.nio.file.Glob import sbt.testing.Framework -import sbt.util.{ cacheLevel, ActionCacheStore, Level, Logger, LoggerContext } +import sbt.util.{ cacheLevel, ActionCacheStore, Digest, Level, Logger, LoggerContext } import xsbti.{ HashedVirtualFileRef, VirtualFile, VirtualFileRef } import xsbti.compile._ import xsbti.compile.analysis.ReadStamps @@ -184,6 +184,7 @@ object Keys { val managedResources = taskKey[Seq[File]]("Resources generated by the build.").withRank(BTask) val resourceDirectories = settingKey[Seq[File]]("List of all resource directories, both managed and unmanaged.").withRank(BPlusSetting) val resources = taskKey[Seq[File]]("All resource files, both managed and unmanaged.").withRank(BTask) + private[sbt] val resourceDigests = taskKey[Seq[Digest]]("All resource files, both managed and unmanaged.").withRank(BTask) // Output paths @cacheLevel(include = Array.empty) @@ -353,8 +354,10 @@ object Keys { // Test Keys val testLoader = taskKey[ClassLoader]("Provides the class loader used for testing.").withRank(DTask) val loadedTestFrameworks = taskKey[Map[TestFramework, Framework]]("Loads Framework definitions from the test loader.").withRank(DTask) + @cacheLevel(include = Array.empty) val definedTests = taskKey[Seq[TestDefinition]]("Provides the list of defined tests.").withRank(BMinusTask) val definedTestNames = taskKey[Seq[String]]("Provides the set of defined test names.").withRank(BMinusTask) + val definedTestDigests = taskKey[Map[String, Digest]]("Provides a unique digest of defined tests.").withRank(DTask) val executeTests = taskKey[Tests.Output]("Executes all tests, producing a report.").withRank(CTask) val test = taskKey[Unit]("Executes all tests.").withRank(APlusTask) val testOnly = inputKey[Unit]("Executes the tests provided as arguments or all tests if no arguments are provided.").withRank(ATask) @@ -368,6 +371,7 @@ object Keys { val testResultLogger = settingKey[TestResultLogger]("Logs results after a test task completes.").withRank(DTask) val testGrouping = taskKey[Seq[Tests.Group]]("Collects discovered tests into groups. Whether to fork and the options for forking are configurable on a per-group basis.").withRank(BMinusTask) val isModule = AttributeKey[Boolean]("isModule", "True if the target is a module.", DSetting) + val extraTestDigests = taskKey[Seq[Digest]]("Extra digests that would invalidate test caching").withRank(DTask) // Classpath/Dependency Management Keys type Classpath = Def.Classpath diff --git a/main/src/main/scala/sbt/RemoteCache.scala b/main/src/main/scala/sbt/RemoteCache.scala index 14bc67ccd..40113ec5d 100644 --- a/main/src/main/scala/sbt/RemoteCache.scala +++ b/main/src/main/scala/sbt/RemoteCache.scala @@ -142,9 +142,7 @@ object RemoteCache { }, pushRemoteCacheConfiguration / publishMavenStyle := true, Compile / packageCache / pushRemoteCacheArtifact := true, - Test / packageCache / pushRemoteCacheArtifact := true, Compile / packageCache / artifact := Artifact(moduleName.value, cachedCompileClassifier), - Test / packageCache / artifact := Artifact(moduleName.value, cachedTestClassifier), remoteCachePom / pushRemoteCacheArtifact := true, remoteCachePom := { val s = streams.value @@ -194,10 +192,7 @@ object RemoteCache { .withResolvers(rs) } ) - ) ++ inConfig(Compile)( - configCacheSettings(compileArtifact(Compile, cachedCompileClassifier)) - ) - ++ inConfig(Test)(configCacheSettings(testArtifact(Test, cachedTestClassifier)))) + ) ++ inConfig(Compile)(configCacheSettings(compileArtifact(Compile, cachedCompileClassifier)))) def getResourceFilePaths() = Def.task { val syncDir = crossTarget.value / (prefix(configuration.value.name) + "sync") @@ -383,19 +378,6 @@ object RemoteCache { ) } - def testArtifact( - configuration: Configuration, - classifier: String - ): Def.Initialize[Task[TestRemoteCacheArtifact]] = Def.task { - TestRemoteCacheArtifact( - Artifact(moduleName.value, classifier), - configuration / packageCache, - (configuration / classDirectory).value, - (configuration / compileAnalysisFile).value, - Defaults.succeededFile((configuration / test / streams).value.cacheDirectory) - ) - } - private def toVersion(v: String): String = s"0.0.0-$v" private lazy val doption = new DownloadOptions diff --git a/main/src/main/scala/sbt/ScriptedPlugin.scala b/main/src/main/scala/sbt/ScriptedPlugin.scala index fcbfb98e0..a98b498d6 100644 --- a/main/src/main/scala/sbt/ScriptedPlugin.scala +++ b/main/src/main/scala/sbt/ScriptedPlugin.scala @@ -67,24 +67,9 @@ object ScriptedPlugin extends AutoPlugin { .map(_.get().head) .value, sbtTestDirectory := sourceDirectory.value / "sbt-test", - libraryDependencies ++= (CrossVersion.partialVersion(scriptedSbt.value) match - case Some((0, 13)) => - Seq( - "org.scala-sbt" % "scripted-sbt" % scriptedSbt.value % ScriptedConf, - "org.scala-sbt" % "sbt-launch" % scriptedSbt.value % ScriptedLaunchConf - ) - case Some((1, _)) => - Seq( - "org.scala-sbt" %% "scripted-sbt" % scriptedSbt.value % ScriptedConf, - "org.scala-sbt" % "sbt-launch" % scriptedSbt.value % ScriptedLaunchConf - ) - case Some((2, _)) => - Seq( - "org.scala-sbt" %% "scripted-sbt-redux" % scriptedSbt.value % ScriptedConf, - "org.scala-sbt" % "sbt-launch" % scriptedSbt.value % ScriptedLaunchConf - ) - case Some((x, y)) => sys error s"Unknown sbt version ${scriptedSbt.value} ($x.$y)" - case None => sys error s"Unknown sbt version ${scriptedSbt.value}" + libraryDependencies ++= Seq( + "org.scala-sbt" %% "scripted-sbt" % scriptedSbt.value % ScriptedConf, + "org.scala-sbt" % "sbt-launch" % scriptedSbt.value % ScriptedLaunchConf ), scriptedClasspath := getJars(ScriptedConf).value, scriptedTests := scriptedTestsTask.value, diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 3c2ab391d..26f03b795 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -14,9 +14,10 @@ import Keys.{ organization, thisProject, autoGeneratedProject } import Def.Setting // import sbt.ProjectExtra.apply import sbt.io.Hash -import sbt.internal.util.Attributed -import sbt.internal.inc.ReflectUtilities -import xsbti.FileConverter +import sbt.internal.util.{ Attributed, StringAttributeMap } +import sbt.internal.inc.{ FileAnalysisStore, ReflectUtilities } +import xsbti.{ FileConverter, VirtualFileRef } +import xsbti.compile.CompileAnalysis trait BuildDef { def projectDefinitions(@deprecated("unused", "") baseDirectory: File): Seq[Project] = projects @@ -33,7 +34,7 @@ trait BuildDef { def rootProject: Option[Project] = None } -private[sbt] object BuildDef { +private[sbt] object BuildDef: val defaultEmpty: BuildDef = new BuildDef { override def projects = Nil } val default: BuildDef = new BuildDef { @@ -78,5 +79,19 @@ private[sbt] object BuildDef { in: Seq[Attributed[_]], converter: FileConverter ): Seq[xsbti.compile.CompileAnalysis] = - in.flatMap(a => Defaults.extractAnalysis(a.metadata, converter)) -} + in.flatMap(a => extractAnalysis(a.metadata, converter)) + + private[sbt] def extractAnalysis( + metadata: StringAttributeMap, + converter: FileConverter + ): Option[CompileAnalysis] = + import sbt.OptionSyntax.* + def asBinary(file: File) = FileAnalysisStore.binary(file).get.asScala + def asText(file: File) = FileAnalysisStore.text(file).get.asScala + for + ref <- metadata.get(Keys.analysis) + file = converter.toPath(VirtualFileRef.of(ref)).toFile + content <- asBinary(file).orElse(asText(file)) + yield content.getAnalysis + +end BuildDef diff --git a/main/src/main/scala/sbt/internal/IncrementalTest.scala b/main/src/main/scala/sbt/internal/IncrementalTest.scala new file mode 100644 index 000000000..32da1a6dd --- /dev/null +++ b/main/src/main/scala/sbt/internal/IncrementalTest.scala @@ -0,0 +1,219 @@ +/* + * 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 +package internal + +import java.io.File +import java.util.concurrent.ConcurrentHashMap +import Keys.{ test, fileConverter, fullClasspath, streams } +import sbt.Def.Initialize +import sbt.internal.inc.Analysis +import sbt.internal.util.Attributed +import sbt.internal.util.Types.const +import sbt.io.{ GlobFilter, IO, NameFilter } +import sbt.protocol.testing.TestResult +import sbt.SlashSyntax0.* +import sbt.util.{ ActionCache, BuildWideCacheConfiguration, CacheLevelTag, Digest } +import sbt.util.CacheImplicits.given +import scala.collection.concurrent +import scala.collection.mutable +import scala.collection.SortedSet +import xsbti.{ FileConverter, HashedVirtualFileRef, VirtualFileRef } + +object IncrementalTest: + def filterTask: Initialize[Task[Seq[String] => Seq[String => Boolean]]] = + Def.task { + val cp = (Keys.test / fullClasspath).value + val s = (Keys.test / streams).value + val digests = (Keys.definedTestDigests).value + val config = Def.cacheConfiguration.value + def hasCachedSuccess(ts: Digest): Boolean = + val input = cacheInput(ts) + ActionCache.exists(input._1, input._2, input._3, config) + def hasSucceeded(className: String): Boolean = digests.get(className) match + case None => false + case Some(ts) => hasCachedSuccess(ts) + args => + for filter <- selectedFilter(args) + yield (test: String) => filter(test) && !hasSucceeded(test) + } + + // cache the test digests against the fullClasspath. + def definedTestDigestTask: Initialize[Task[Map[String, Digest]]] = Def.cachedTask { + val cp = (Keys.test / fullClasspath).value + val testNames = Keys.definedTests.value.map(_.name).toVector.distinct + val converter = fileConverter.value + val rds = Keys.resourceDigests.value + val extra = Keys.extraTestDigests.value + val stamper = ClassStamper(cp, converter) + // TODO: Potentially do something about JUnit 5 and others which might not use class name + Map((testNames.flatMap: name => + stamper.transitiveStamp(name, extra ++ rds) match + case Some(ts) => Seq(name -> ts) + case None => Nil + ): _*) + } + + def extraTestDigestsTask: Initialize[Task[Seq[Digest]]] = Def.cachedTask { + // by default this captures JVM version + val extraInc = Keys.extraIncOptions.value + // throw in any information useful for runtime invalidation + val salt = s"""${extraInc.mkString(",")} +""" + Vector(Digest.sha256Hash(salt.getBytes("UTF-8"))) + } + + 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))) + + private[sbt] def cacheInput(value: Digest): (Unit, Digest, Digest) = + ((), value, Digest.zero) +end IncrementalTest + +private[sbt] class TestStatusReporter( + digests: Map[String, Digest], + cacheConfiguration: BuildWideCacheConfiguration, +) extends TestsListener: + // int value to represent success + private final val successfulTest = 0 + + def doInit(): Unit = () + def startGroup(name: String): Unit = () + def testEvent(event: TestEvent): Unit = () + def endGroup(name: String, t: Throwable): Unit = () + + /** + * If the test has succeeded, record the fact that it has + * using its unique digest, so we can skip the test later. + */ + def endGroup(name: String, result: TestResult): Unit = + if result == TestResult.Passed then + digests.get(name) match + case Some(ts) => + // treat each test suite as a successful action that returns 0 + val input = IncrementalTest.cacheInput(ts) + ActionCache.cache( + key = input._1, + codeContentHash = input._2, + extraHash = input._3, + tags = CacheLevelTag.all.toList, + config = cacheConfiguration, + ): (_) => + ActionCache.actionResult(successfulTest) + case None => () + else () + def doComplete(finalResult: TestResult): Unit = () +end TestStatusReporter + +private[sbt] object TestStatus: + import java.util.Properties + def read(f: File): concurrent.Map[String, Digest] = + import scala.jdk.CollectionConverters.* + val props = Properties() + IO.load(props, f) + val result = ConcurrentHashMap[String, Digest]() + props.asScala.iterator.foreach { case (k, v) => result.put(k, Digest(v)) } + result.asScala + + def write(map: collection.Map[String, Digest], label: String, f: File): Unit = + IO.writeLines( + f, + s"# $label" :: + map.toList.sortBy(_._1).map { case (k, v) => + s"$k=$v" + } + ) +end TestStatus + +/** + * ClassStamper provides `transitiveStamp` method to calculate a unique + * fingerprint, which will be used for runtime invalidation. + */ +class ClassStamper( + classpath: Seq[Attributed[HashedVirtualFileRef]], + converter: FileConverter, +): + private val stamps = mutable.Map.empty[String, SortedSet[Digest]] + private val vfStamps = mutable.Map.empty[VirtualFileRef, Digest] + private lazy val analyses = classpath + .flatMap(a => BuildDef.extractAnalysis(a.metadata, converter)) + .collect { case analysis: Analysis => analysis } + + /** + * Given a classpath and a class name, this tries to create a SHA-256 digest. + * @param className className to stamp + * @param extraHashes additional information to include into the returning digest + */ + private[sbt] def transitiveStamp(className: String, extaHashes: Seq[Digest]): Option[Digest] = + val digests = SortedSet(analyses.flatMap(internalStamp(className, _, Set.empty)): _*) + if digests.nonEmpty then Some(Digest.sha256Hash(digests.toSeq ++ extaHashes: _*)) + else None + + private def internalStamp( + className: String, + analysis: Analysis, + alreadySeen: Set[String], + ): SortedSet[Digest] = + if alreadySeen.contains(className) then SortedSet.empty + else + stamps.get(className) match + case Some(xs) => xs + case _ => + import analysis.relations + val internalDeps = relations + .internalClassDeps(className) + .flatMap: otherCN => + internalStamp(otherCN, analysis, alreadySeen + className) + val internalJarDeps = relations + .externalDeps(className) + .flatMap: libClassName => + transitiveStamp(libClassName, Nil) + val externalDeps = relations + .externalDeps(className) + .flatMap: libClassName => + relations.libraryClassName + .reverse(libClassName) + .map(stampVf) + val classDigests = relations.productClassName + .reverse(className) + .flatMap: prodClassName => + relations + .definesClass(prodClassName) + .flatMap: sourceFile => + relations + .products(sourceFile) + .map(stampVf) + // TODO: substitue the above with + // val classDigests = relations.productClassName + // .reverse(className) + // .flatMap: prodClassName => + // analysis.apis.internal + // .get(prodClassName) + // .map: analyzed => + // 0L // analyzed.??? we need a hash here + val xs = SortedSet( + (internalDeps union internalJarDeps union externalDeps union classDigests).toSeq: _* + ) + if xs.nonEmpty then stamps(className) = xs + else () + xs + def stampVf(vf: VirtualFileRef): Digest = + vf match + case h: HashedVirtualFileRef => Digest(h) + case _ => + vfStamps.getOrElseUpdate(vf, Digest.sha256Hash(converter.toPath(vf))) +end ClassStamper diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 136be4f7a..a555d1f57 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -839,11 +839,11 @@ private[sbt] object Load { defaultProjects.generatedConfigClassFiles ++ loadedProjectsRaw.generatedConfigClassFiles ) } - // Now we clean stale class files. - // TODO - this may cause issues with multiple sbt clients, but that should be deprecated pending sbt-server anyway - timed("Load.loadUnit: cleanEvalClasses", log) { - cleanEvalClasses(defDir, keepClassFiles) - } + // TODO: Uncomment when we fixed https://github.com/sbt/sbt/issues/7424 + // likely keepClassFiles isn't covering enough. + // timed("Load.loadUnit: cleanEvalClasses", log) { + // cleanEvalClasses(defDir, keepClassFiles) + // } val defs = if (defsScala.isEmpty) defaultBuildIfNone :: Nil else defsScala // HERE we pull out the defined vals from memoSettings and unify them all so // we can use them later. diff --git a/main/src/main/scala/sbt/nio/FileStamp.scala b/main/src/main/scala/sbt/nio/FileStamp.scala index 4194c9dc2..6e8ddaf3a 100644 --- a/main/src/main/scala/sbt/nio/FileStamp.scala +++ b/main/src/main/scala/sbt/nio/FileStamp.scala @@ -13,8 +13,10 @@ import java.nio.file.{ Path, Paths } import java.util.concurrent.ConcurrentHashMap import sbt.internal.inc.{ EmptyStamp, Stamper, Hash => IncHash, LastModified => IncLastModified } +import sbt.internal.inc.JavaInterfaceUtil.given import sbt.io.IO import sbt.nio.file.FileAttributes +import sbt.util.Digest import sjsonnew.{ Builder, JsonFormat, Unbuilder, deserializationError } import xsbti.compile.analysis.{ Stamp => XStamp } import xsbti.VirtualFileRef @@ -103,6 +105,15 @@ object FileStamp { private[sbt] final case class LastModified private[sbt] (time: Long) extends FileStamp private[sbt] final case class Error(exception: IOException) extends FileStamp + def toDigest(path: Path, stamp: FileStamp): Digest = stamp match + case f: FileHashImpl => + f.xstamp.getHash().toOption match + case Some(hash) => Digest.sha256Hash(hash.getBytes("UTF-8")) + case None => Digest.sha256Hash(path) + case FileStamp.Hash(hex) => Digest.sha256Hash(hex.getBytes("UTF-8")) + case FileStamp.Error(_) => Digest.zero + case FileStamp.LastModified(_) => Digest.sha256Hash(path) + object Formats { implicit val seqPathJsonFormatter: JsonFormat[Seq[Path]] = asStringArray(_.toString, Paths.get(_)) diff --git a/main/src/test/scala/DefaultsTest.scala b/main/src/test/scala/SelectedFilterTest.scala similarity index 63% rename from main/src/test/scala/DefaultsTest.scala rename to main/src/test/scala/SelectedFilterTest.scala index 3e02ac0c8..6a08ce558 100644 --- a/main/src/test/scala/DefaultsTest.scala +++ b/main/src/test/scala/SelectedFilterTest.scala @@ -8,13 +8,17 @@ package sbt +import sbt.internal.IncrementalTest + object DefaultsTest extends verify.BasicTestSuite { test("`selectedFilter` should return all tests for an empty list") { val expected = Map("Test1" -> true, "Test2" -> true) val filter = List.empty[String] assert( - expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) == expected + expected.map(t => + (t._1, IncrementalTest.selectedFilter(filter).exists(fn => fn(t._1))) + ) == expected ) } @@ -22,7 +26,9 @@ object DefaultsTest extends verify.BasicTestSuite { val expected = Map("Test1" -> true, "Test2" -> false, "Foo" -> false) val filter = List("Test1", "foo") assert( - expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) == expected + expected.map(t => + (t._1, IncrementalTest.selectedFilter(filter).exists(fn => fn(t._1))) + ) == expected ) } @@ -30,7 +36,9 @@ object DefaultsTest extends verify.BasicTestSuite { val expected = Map("Test1" -> true, "Test2" -> true, "Foo" -> false) val filter = List("Test*") assert( - expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) == expected + expected.map(t => + (t._1, IncrementalTest.selectedFilter(filter).exists(fn => fn(t._1))) + ) == expected ) } @@ -38,7 +46,9 @@ object DefaultsTest extends verify.BasicTestSuite { val expected = Map("Test1" -> true, "Test2" -> false, "Foo" -> false) val filter = List("Test*", "-Test2") assert( - expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) == expected + expected.map(t => + (t._1, IncrementalTest.selectedFilter(filter).exists(fn => fn(t._1))) + ) == expected ) } @@ -46,7 +56,9 @@ object DefaultsTest extends verify.BasicTestSuite { val expected = Map("Test1" -> true, "Test2" -> false, "Foo" -> true) val filter = List("-Test2") assert( - expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) == expected + expected.map(t => + (t._1, IncrementalTest.selectedFilter(filter).exists(fn => fn(t._1))) + ) == expected ) } @@ -54,7 +66,9 @@ object DefaultsTest extends verify.BasicTestSuite { val expected = Map("Test1" -> true, "Test2" -> true, "Foo" -> false) val filter = List("Test*", "-F*") assert( - expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) == expected + expected.map(t => + (t._1, IncrementalTest.selectedFilter(filter).exists(fn => fn(t._1))) + ) == expected ) } @@ -62,7 +76,9 @@ object DefaultsTest extends verify.BasicTestSuite { val expected = Map("Test1" -> true, "Test2" -> true, "Foo" -> false) val filter = List("T*1", "T*2", "-F*") assert( - expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) == expected + expected.map(t => + (t._1, IncrementalTest.selectedFilter(filter).exists(fn => fn(t._1))) + ) == expected ) } @@ -70,7 +86,9 @@ object DefaultsTest extends verify.BasicTestSuite { val expected = Map("Test1" -> true, "Test2" -> true, "AAA" -> false, "Foo" -> false) val filter = List("-A*", "-F*") assert( - expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) == expected + expected.map(t => + (t._1, IncrementalTest.selectedFilter(filter).exists(fn => fn(t._1))) + ) == expected ) } @@ -78,7 +96,9 @@ object DefaultsTest extends verify.BasicTestSuite { val expected = Map("Test1" -> false, "Test2" -> false, "Test3" -> true) val filter = List("T*", "-T*1", "-T*2") assert( - expected.map(t => (t._1, Defaults.selectedFilter(filter).exists(fn => fn(t._1)))) == expected + expected.map(t => + (t._1, IncrementalTest.selectedFilter(filter).exists(fn => fn(t._1))) + ) == expected ) } } diff --git a/project/build.properties b/project/build.properties index 081fdbbc7..0b699c305 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.0 +sbt.version=1.10.2 diff --git a/sbt-app/src/sbt-test/actions/eval-is-safe-and-sound/test b/sbt-app/src/sbt-test/actions/eval-is-safe-and-sound/pending similarity index 100% rename from sbt-app/src/sbt-test/actions/eval-is-safe-and-sound/test rename to sbt-app/src/sbt-test/actions/eval-is-safe-and-sound/pending diff --git a/sbt-app/src/sbt-test/classloader-cache/resources/test b/sbt-app/src/sbt-test/classloader-cache/resources/test index 63aa35f05..a41abee03 100644 --- a/sbt-app/src/sbt-test/classloader-cache/resources/test +++ b/sbt-app/src/sbt-test/classloader-cache/resources/test @@ -4,12 +4,12 @@ $ copy-file changes/updated-main.txt src/main/resources/foo.txt > run foo.txt updated-main -> test +> testQuick $ copy-file changes/updated-test.txt src/test/resources/bar.txt --> test +-> testQuick $ copy-file changes/UpdatedResourceTest.scala src/test/scala/scripted/ResourceTest.scala -> test +> testQuick diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/build.sbt b/sbt-app/src/sbt-test/classloader-cache/snapshot/build.sbt deleted file mode 100644 index 4d93e6bb6..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/build.sbt +++ /dev/null @@ -1,31 +0,0 @@ -ThisBuild / turbo := true -ThisBuild / scalaVersion := "2.12.17" - -import java.nio.file.Files -import java.nio.file.attribute.FileTime -import scala.jdk.CollectionConverters.* -val rewriteIvy = inputKey[Unit]("Rewrite ivy directory") - -ThisBuild / useCoursier := false - -val snapshot = (project in file(".")).settings( - name := "akka-test", - scalaVersion := "2.12.19", - libraryDependencies ++= Seq( - "com.lihaoyi" %% "utest" % "0.6.6" % "test" - ), - testFrameworks += TestFramework("utest.runner.Framework"), - resolvers += "Local Maven" at file("ivy").toURI.toURL.toString, - libraryDependencies += "sbt" %% "foo-lib" % "0.1.0-SNAPSHOT", - rewriteIvy := { - val dir = Def.spaceDelimited().parsed.head - sbt.IO.delete(baseDirectory.value / "ivy") - sbt.IO.copyDirectory( - baseDirectory.value / s"libraries/library-$dir/ivy", - baseDirectory.value / "ivy" - ) - Files.walk(file("ivy").getCanonicalFile.toPath).iterator.asScala.foreach { f => - Files.setLastModifiedTime(f, FileTime.fromMillis(System.currentTimeMillis + 3000)) - } - } -) diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/build.sbt b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/build.sbt deleted file mode 100644 index 55109c35f..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/build.sbt +++ /dev/null @@ -1,5 +0,0 @@ -name := "foo-lib" - -organization := "sbt" - -publishTo := Some(Resolver.file("test-resolver", file("").getCanonicalFile / "ivy")) diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar deleted file mode 100644 index 6ca76e2fd..000000000 Binary files a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar and /dev/null differ diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 deleted file mode 100644 index bc15f9443..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -397429ea4a937c9ad21268ac7f294c9b \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 deleted file mode 100644 index c36a79d59..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -183362cade58c89ae813465e7c509a3346515e39 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar deleted file mode 100644 index f7acb17ad..000000000 Binary files a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar and /dev/null differ diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 deleted file mode 100644 index 9cadb3a11..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -9c46dbde2cd9c996c37c7ba4461eec7c \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 deleted file mode 100644 index b4a982ea0..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -b5725ff80281d86491166550058b56c6b0b7dd2d \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar deleted file mode 100644 index 197c6e591..000000000 Binary files a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar and /dev/null differ diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 deleted file mode 100644 index 0861ac32f..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -c39385e52b24880f549e1c01642ed010 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 deleted file mode 100644 index 5d81c7d99..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -de497479331da6c5dff72b6c1cfa5fca1634933f \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom deleted file mode 100644 index 233c03dbe..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom +++ /dev/null @@ -1,20 +0,0 @@ - - - 4.0.0 - sbt - foo-lib_2.12 - jar - foo-lib - 0.1.0-SNAPSHOT - foo-lib - - sbt - - - - org.scala-lang - scala-library - 2.12.19 - - - diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 deleted file mode 100644 index 394b7d529..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 +++ /dev/null @@ -1 +0,0 @@ -ada0c2e0276459449bf25a4f99aab3f6 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 deleted file mode 100644 index 7162aec5e..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 +++ /dev/null @@ -1 +0,0 @@ -466216207d16d3e0daf0b2b18e67b20882f7f1b5 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/project/build.properties b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/project/build.properties deleted file mode 100644 index 7c58a83ab..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version=1.2.6 diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/src/main/scala/sbt/Foo.scala b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/src/main/scala/sbt/Foo.scala deleted file mode 100644 index 7b95dd3d2..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-1/src/main/scala/sbt/Foo.scala +++ /dev/null @@ -1,5 +0,0 @@ -package sbt - -object Foo { - def x: Int = 1 -} diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/build.sbt b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/build.sbt deleted file mode 100644 index 55109c35f..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/build.sbt +++ /dev/null @@ -1,5 +0,0 @@ -name := "foo-lib" - -organization := "sbt" - -publishTo := Some(Resolver.file("test-resolver", file("").getCanonicalFile / "ivy")) diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar deleted file mode 100644 index 8505dca48..000000000 Binary files a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar and /dev/null differ diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 deleted file mode 100644 index 249153493..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -260c1aab64032676c2c5b7d3c2a8e385 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 deleted file mode 100644 index ceb465ee3..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -44c78fcc5a2918bc4b33afb7419ac9d643bbac26 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar deleted file mode 100644 index 5ef80e248..000000000 Binary files a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar and /dev/null differ diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 deleted file mode 100644 index 8a81b3699..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -39c7358e50708bc3da53ffe34eb876d6 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 deleted file mode 100644 index 263e42fd8..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -64e078d3fed51c0ef3d8abdb096ab1406e44ee1c \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar deleted file mode 100644 index c41eddd31..000000000 Binary files a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar and /dev/null differ diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 deleted file mode 100644 index c7bb182af..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -763822fba242624131cf0706be56dabf \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 deleted file mode 100644 index 6c5f9adf1..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -22192abf4b52e85b9f979aa883ab42ce33bc51cd \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom deleted file mode 100644 index 233c03dbe..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom +++ /dev/null @@ -1,20 +0,0 @@ - - - 4.0.0 - sbt - foo-lib_2.12 - jar - foo-lib - 0.1.0-SNAPSHOT - foo-lib - - sbt - - - - org.scala-lang - scala-library - 2.12.19 - - - diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 deleted file mode 100644 index 394b7d529..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 +++ /dev/null @@ -1 +0,0 @@ -ada0c2e0276459449bf25a4f99aab3f6 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 deleted file mode 100644 index 7162aec5e..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 +++ /dev/null @@ -1 +0,0 @@ -466216207d16d3e0daf0b2b18e67b20882f7f1b5 \ No newline at end of file diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/project/build.properties b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/project/build.properties deleted file mode 100644 index 7c58a83ab..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version=1.2.6 diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/src/main/scala/sbt/Foo.scala b/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/src/main/scala/sbt/Foo.scala deleted file mode 100644 index 75cca6b19..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/libraries/library-2/src/main/scala/sbt/Foo.scala +++ /dev/null @@ -1,5 +0,0 @@ -package sbt - -object Foo { - def x: Int = 2 -} diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/src/test/scala/sbt/SnapshotTest.scala b/sbt-app/src/sbt-test/classloader-cache/snapshot/src/test/scala/sbt/SnapshotTest.scala deleted file mode 100644 index fe242766a..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/src/test/scala/sbt/SnapshotTest.scala +++ /dev/null @@ -1,11 +0,0 @@ -package sbt - -import utest._ - -object SnapshotTest extends TestSuite { - val tests: Tests = Tests { - 'foo - { - com.swoval.Foo.x ==> 1 - } - } -} diff --git a/sbt-app/src/sbt-test/classloader-cache/snapshot/test b/sbt-app/src/sbt-test/classloader-cache/snapshot/test deleted file mode 100644 index 179d9c36c..000000000 --- a/sbt-app/src/sbt-test/classloader-cache/snapshot/test +++ /dev/null @@ -1,5 +0,0 @@ -> rewriteIvy 1 -# If the ClassLoaderCache is not correctly set up, then the sbt testing framework fails to work correctly -> test -> rewriteIvy 2 --> test diff --git a/scripted-plugin/src/main/scala/sbt/ScriptedPlugin.scala b/scripted-plugin/src/main/scala/sbt/ScriptedPlugin.scala deleted file mode 100644 index 8383a97f3..000000000 --- a/scripted-plugin/src/main/scala/sbt/ScriptedPlugin.scala +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 - -// ScriptedPlugin has moved to main. -private[sbt] object ScriptedPluginNote diff --git a/scripted-sbt-old/src/main/scala/sbt/test/OldScriptedTests.scala b/scripted-sbt-old/src/main/scala/sbt/test/OldScriptedTests.scala deleted file mode 100644 index 0eaf16602..000000000 --- a/scripted-sbt-old/src/main/scala/sbt/test/OldScriptedTests.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.test - -import java.io.File - -/** - * This is a bincompat place holder sbt.test package that we are now trying to hide - * because of the name conflict with Keys.test. - */ -@deprecated("Use sbt.scriptedtest.ScriptedRunner.", "1.2.0") -private[sbt] class ScriptedRunner extends sbt.scriptedtest.ScriptedRunner - -/** - * This is a bincompat place holder for sbt.test package that we are now trying to hide - * because of the name conflict with Keys.test. - */ -@deprecated("Use sbt.scriptedtest.ScriptedTests.", "1.2.0") -private[sbt] object ScriptedTests extends ScriptedRunner { - - /** Represents the function that runs the scripted tests, both in single or batch mode. */ - type TestRunner = () => Seq[Option[String]] - - val emptyCallback: File => Unit = _ => () - def main(args: Array[String]): Unit = - sbt.scriptedtest.ScriptedTests.main(args) -} diff --git a/scripted-sbt-redux/NOTICE b/scripted-sbt/NOTICE similarity index 100% rename from scripted-sbt-redux/NOTICE rename to scripted-sbt/NOTICE diff --git a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala b/scripted-sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala similarity index 100% rename from scripted-sbt-redux/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala rename to scripted-sbt/src/main/scala/sbt/scriptedtest/BatchScriptRunner.scala diff --git a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/RemoteSbtCreator.scala b/scripted-sbt/src/main/scala/sbt/scriptedtest/RemoteSbtCreator.scala similarity index 100% rename from scripted-sbt-redux/src/main/scala/sbt/scriptedtest/RemoteSbtCreator.scala rename to scripted-sbt/src/main/scala/sbt/scriptedtest/RemoteSbtCreator.scala diff --git a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/SbtHandler.scala b/scripted-sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala similarity index 100% rename from scripted-sbt-redux/src/main/scala/sbt/scriptedtest/SbtHandler.scala rename to scripted-sbt/src/main/scala/sbt/scriptedtest/SbtHandler.scala diff --git a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala similarity index 100% rename from scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala rename to scripted-sbt/src/main/scala/sbt/scriptedtest/ScriptedTests.scala diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index e350d9bf1..4a5f47304 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -93,6 +93,7 @@ final class TestFramework(val implClassNames: String*) extends Serializable { def create(loader: ClassLoader, log: ManagedLogger): Option[Framework] = createFramework(loader, log, implClassNames.toList) } + final class TestDefinition( val name: String, val fingerprint: Fingerprint, @@ -108,7 +109,7 @@ final class TestDefinition( override def hashCode: Int = (name.hashCode, TestFramework.hashCode(fingerprint)).hashCode } -final class TestRunner( +private[sbt] final class TestRunner( delegate: Runner, listeners: Vector[TestReportListener], log: ManagedLogger @@ -214,7 +215,7 @@ object TestFramework { case _ => f.toString } - def testTasks( + private[sbt] def testTasks( frameworks: Map[TestFramework, Framework], runners: Map[TestFramework, Runner], testLoader: ClassLoader, diff --git a/testing/src/main/scala/sbt/TestStatusReporter.scala b/testing/src/main/scala/sbt/TestStatusReporter.scala deleted file mode 100644 index 4eb110dac..000000000 --- a/testing/src/main/scala/sbt/TestStatusReporter.scala +++ /dev/null @@ -1,53 +0,0 @@ -/* - * sbt - * Copyright 2023, Scala center - * Copyright 2011 - 2022, Lightbend, Inc. - * Copyright 2008 - 2010, Mark Harrah - * Licensed under Apache License 2.0 (see LICENSE) - */ - -package sbt - -import java.io.File - -import sbt.io.IO -import sbt.protocol.testing.TestResult -import java.util.concurrent.ConcurrentHashMap - -import scala.collection.concurrent - -// Assumes exclusive ownership of the file. -private[sbt] class TestStatusReporter(f: File) extends TestsListener { - private lazy val succeeded: concurrent.Map[String, Long] = TestStatus.read(f) - - def doInit(): Unit = () - def startGroup(name: String): Unit = { succeeded remove name; () } - def testEvent(event: TestEvent): Unit = () - def endGroup(name: String, t: Throwable): Unit = () - def endGroup(name: String, result: TestResult): Unit = { - if (result == TestResult.Passed) - succeeded(name) = System.currentTimeMillis - } - def doComplete(finalResult: TestResult): Unit = { - TestStatus.write(succeeded, "Successful Tests", f) - } -} - -private[sbt] object TestStatus { - import java.util.Properties - def read(f: File): concurrent.Map[String, Long] = { - import scala.jdk.CollectionConverters.* - val properties = new Properties - IO.load(properties, f) - val result = new ConcurrentHashMap[String, Long]() - properties.asScala.iterator.foreach { case (k, v) => result.put(k, v.toLong) } - result.asScala - } - - def write(map: collection.Map[String, Long], label: String, f: File): Unit = { - val properties = new Properties - for ((test, lastSuccessTime) <- map) - properties.setProperty(test, lastSuccessTime.toString) - IO.write(properties, label, f) - } -} diff --git a/util-cache/src/main/scala/sbt/util/ActionCache.scala b/util-cache/src/main/scala/sbt/util/ActionCache.scala index 0509a6e04..33c3042a2 100644 --- a/util-cache/src/main/scala/sbt/util/ActionCache.scala +++ b/util-cache/src/main/scala/sbt/util/ActionCache.scala @@ -31,24 +31,19 @@ object ActionCache: * Even if the input tasks are the same, the code part needs to be tracked. * - extraHash: Reserved for later, which we might use to invalidate the cache. * - tags: Tags to track cache level. - * - action: The actual action to be cached. * - config: The configuration that's used to store where the cache backends are. + * - action: The actual action to be cached. */ def cache[I: HashWriter, O: JsonFormat: ClassTag]( key: I, codeContentHash: Digest, extraHash: Digest, tags: List[CacheLevelTag], + config: BuildWideCacheConfiguration, )( action: I => InternalActionResult[O], - )( - config: BuildWideCacheConfiguration ): O = import config.* - val input = - Digest.sha256Hash(codeContentHash, extraHash, Digest.dummy(Hasher.hashUnsafe[I](key))) - val valuePath = s"value/${input}.json" - def organicTask: O = // run action(...) and combine the newResult with outputs val InternalActionResult(result, outputs) = @@ -69,38 +64,80 @@ object ActionCache: result else cacheEventLog.append(ActionCacheEvent.OnsiteTask) + val input = mkInput(key, codeContentHash, extraHash) val valueFile = StringVirtualFile1(s"value/${input}.json", CompactPrinter(json)) val newOutputs = Vector(valueFile) ++ outputs.toVector store.put(UpdateActionResultRequest(input, newOutputs, exitCode = 0)) match case Right(cachedResult) => - syncBlobs(cachedResult.outputFiles) + store.syncBlobs(cachedResult.outputFiles, outputDirectory) result case Left(e) => throw e + get(key, codeContentHash, extraHash, tags, config) match + case Some(value) => value + case None => organicTask + end cache + + /** + * Retrieves the cached value. + */ + def get[I: HashWriter, O: JsonFormat: ClassTag]( + key: I, + codeContentHash: Digest, + extraHash: Digest, + tags: List[CacheLevelTag], + config: BuildWideCacheConfiguration, + ): Option[O] = + import config.store def valueFromStr(str: String, origin: Option[String]): O = - cacheEventLog.append(ActionCacheEvent.Found(origin.getOrElse("unknown"))) + config.cacheEventLog.append(ActionCacheEvent.Found(origin.getOrElse("unknown"))) val json = Parser.parseUnsafe(str) Converter.fromJsonUnsafe[O](json) - - def syncBlobs(refs: Seq[HashedVirtualFileRef]): Seq[Path] = - store.syncBlobs(refs, config.outputDirectory) - - val getRequest = - GetActionResultRequest(input, inlineStdout = false, inlineStderr = false, Vector(valuePath)) - store.get(getRequest) match + findActionResult(key, codeContentHash, extraHash, config) match case Right(result) => // some protocol can embed values into the result result.contents.headOption match case Some(head) => - syncBlobs(result.outputFiles) + store.syncBlobs(result.outputFiles, config.outputDirectory) val str = String(head.array(), StandardCharsets.UTF_8) - valueFromStr(str, result.origin) + Some(valueFromStr(str, result.origin)) case _ => - val paths = syncBlobs(result.outputFiles) - if paths.isEmpty then organicTask - else valueFromStr(IO.read(paths.head.toFile()), result.origin) - case Left(_) => organicTask - end cache + val paths = store.syncBlobs(result.outputFiles, config.outputDirectory) + if paths.isEmpty then None + else Some(valueFromStr(IO.read(paths.head.toFile()), result.origin)) + case Left(_) => None + + /** + * Checks if the ActionResult exists in the cache. + */ + def exists[I: HashWriter]( + key: I, + codeContentHash: Digest, + extraHash: Digest, + config: BuildWideCacheConfiguration, + ): Boolean = + findActionResult(key, codeContentHash, extraHash, config) match + case Right(_) => true + case Left(_) => false + + inline private[sbt] def findActionResult[I: HashWriter, O]( + key: I, + codeContentHash: Digest, + extraHash: Digest, + config: BuildWideCacheConfiguration, + ): Either[Throwable, ActionResult] = + val input = mkInput(key, codeContentHash, extraHash) + val valuePath = s"value/${input}.json" + val getRequest = + GetActionResultRequest(input, inlineStdout = false, inlineStderr = false, Vector(valuePath)) + config.store.get(getRequest) + + private inline def mkInput[I: HashWriter]( + key: I, + codeContentHash: Digest, + extraHash: Digest + ): Digest = + Digest.sha256Hash(codeContentHash, extraHash, Digest.dummy(Hasher.hashUnsafe[I](key))) def manifestFromFile(manifest: Path): Manifest = import sbt.internal.util.codec.ManifestCodec.given @@ -146,6 +183,9 @@ object ActionCache: IO.zip((allPaths ++ Seq(mPath)).flatMap(rebase), zipPath.toFile(), Some(default2010Timestamp)) conv.toVirtualFile(zipPath) + inline def actionResult[A1](inline value: A1): InternalActionResult[A1] = + InternalActionResult(value, Nil) + /** * Represents a value and output files, used internally by the macro. */ diff --git a/util-cache/src/main/scala/sbt/util/ActionCacheStore.scala b/util-cache/src/main/scala/sbt/util/ActionCacheStore.scala index e4e405465..0b243663c 100644 --- a/util-cache/src/main/scala/sbt/util/ActionCacheStore.scala +++ b/util-cache/src/main/scala/sbt/util/ActionCacheStore.scala @@ -2,7 +2,8 @@ package sbt.util import java.io.RandomAccessFile import java.nio.ByteBuffer -import java.nio.file.{ Files, Path, Paths } +import java.nio.file.{ Files, FileSystemException, Path, Paths, StandardCopyOption } +import java.util.concurrent.atomic.AtomicBoolean import sjsonnew.* import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser } import sjsonnew.shaded.scalajson.ast.unsafe.JValue @@ -14,7 +15,7 @@ import sbt.io.IO import sbt.io.syntax.* import sbt.nio.file.{ **, FileTreeView } import sbt.nio.file.syntax.* -import sbt.internal.util.StringVirtualFile1 +import sbt.internal.util.{ StringVirtualFile1, Util } import sbt.internal.util.codec.ActionResultCodec.given import xsbti.{ HashedVirtualFileRef, PathBasedFile, VirtualFile } import java.io.InputStream @@ -182,6 +183,8 @@ class DiskActionCacheStore(base: Path) extends AbstractActionCacheStore: dir } + private val symlinkSupported: AtomicBoolean = AtomicBoolean(true) + override def storeName: String = "disk" override def get(request: GetActionResultRequest): Either[Throwable, ActionResult] = val acFile = acBase.toFile / request.actionDigest.toString.replace("/", "-") @@ -263,24 +266,44 @@ class DiskActionCacheStore(base: Path) extends AbstractActionCacheStore: if ref.id.startsWith("${OUT}/") then ref.id.drop(7) else ref.id val d = Digest(ref) - def symlinkAndNotify(outPath: Path): Path = + def copyFile(outPath: Path): Path = + Files.copy( + casFile, + outPath, + StandardCopyOption.COPY_ATTRIBUTES, + StandardCopyOption.REPLACE_EXISTING, + ) + // See https://github.com/sbt/sbt/issues/7656 + // On Windows, the program has be running under the Administrator privileges or the + // user enable Developer Mode on Windows 10+ to create symbolic links. + def writeFileAndNotify(outPath: Path): Path = Files.createDirectories(outPath.getParent()) val result = Retry: if Files.exists(outPath) then IO.delete(outPath.toFile()) - Files.createSymbolicLink(outPath, casFile) + if symlinkSupported.get() then + try Files.createSymbolicLink(outPath, casFile) + catch + case e: FileSystemException => + if Util.isWindows then + scala.Console.err.println( + "[info] failed to a create symbolic link. consider enabling Developer Mode" + ) + symlinkSupported.set(false) + copyFile(outPath) + else copyFile(outPath) afterFileWrite(ref, result, outputDirectory) result outputDirectory.resolve(shortPath) match case p if !Files.exists(p) => // println(s"- syncFile: $p does not exist") - symlinkAndNotify(p) + writeFileAndNotify(p) case p if Digest.sameDigest(p, d) => // println(s"- syncFile: $p has same digest") p case p => // println(s"- syncFile: $p has different digest") IO.delete(p.toFile()) - symlinkAndNotify(p) + writeFileAndNotify(p) /** * Emulate virtual side effects. diff --git a/util-cache/src/main/scala/sbt/util/Digest.scala b/util-cache/src/main/scala/sbt/util/Digest.scala index a6fbc8b06..b7c1a5ee6 100644 --- a/util-cache/src/main/scala/sbt/util/Digest.scala +++ b/util-cache/src/main/scala/sbt/util/Digest.scala @@ -26,6 +26,10 @@ object Digest: def toBytes: Array[Byte] = parse(d)._4 def sizeBytes: Long = parse(d)._3 + given digestOrd(using ord: Ordering[String]): Ordering[Digest] with + def compare(x: Digest, y: Digest) = + ord.compare(x, y) + def apply(s: String): Digest = validateString(s) s diff --git a/util-cache/src/test/scala/sbt/util/ActionCacheTest.scala b/util-cache/src/test/scala/sbt/util/ActionCacheTest.scala index ea676cead..dea419e1a 100644 --- a/util-cache/src/test/scala/sbt/util/ActionCacheTest.scala +++ b/util-cache/src/test/scala/sbt/util/ActionCacheTest.scala @@ -43,10 +43,10 @@ object ActionCacheTest extends BasicTestSuite: IO.withTemporaryDirectory: (tempDir) => val config = getCacheConfig(cache, tempDir) val v1 = - ActionCache.cache((1, 1), Digest.zero, Digest.zero, tags)(action)(config) + ActionCache.cache((1, 1), Digest.zero, Digest.zero, tags, config)(action) assert(v1 == 2) val v2 = - ActionCache.cache((1, 1), Digest.zero, Digest.zero, tags)(action)(config) + ActionCache.cache((1, 1), Digest.zero, Digest.zero, tags, config)(action) assert(v2 == 2) // check that the action has been invoked only once assert(called == 1) @@ -65,7 +65,7 @@ object ActionCacheTest extends BasicTestSuite: } val config = getCacheConfig(cache, tempDir) val v1 = - ActionCache.cache((1, 1), Digest.zero, Digest.zero, tags)(action)(config) + ActionCache.cache((1, 1), Digest.zero, Digest.zero, tags, config)(action) assert(v1 == 2) // ActionResult only contains the reference to the files. // To retrieve them, separately call readBlobs or syncBlobs. @@ -75,7 +75,7 @@ object ActionCacheTest extends BasicTestSuite: assert(content == "2") val v2 = - ActionCache.cache((1, 1), Digest.zero, Digest.zero, tags)(action)(config) + ActionCache.cache((1, 1), Digest.zero, Digest.zero, tags, config)(action) assert(v2 == 2) // check that the action has been invoked only once assert(called == 1)