From 82f03cc6f4caaad9c273b3e01d297a0b7284aef6 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 27 Aug 2026 02:04:47 -0400 Subject: [PATCH 1/2] Cross build for sbt 1.x and 2.x --- .github/workflows/ci.yml | 4 +- .gitignore | 1 + .../dbuild/logging/ActorLogging.scala | 14 +- .../project/build/BuildRunnerActor.scala | 38 +- .../project/dependencies/ExtractorActor.scala | 44 +- adapter/src/main/scala-2.10/Adapter.scala | 88 -- adapter/src/main/scala-2.12/Adapter.scala | 155 +--- adapter/src/main/scala-3/Adapter.scala | 59 ++ adapter/src/main/scala/AdapterCommon.scala | 127 +++ adapter/src/main/scala/DbuildHack.scala | 17 + build.sbt | 69 +- .../typesafe/dbuild/build/RewireSpec.scala | 154 ++-- .../com/typesafe/dbuild/build/Checkout.scala | 28 +- .../typesafe/dbuild/build/Comparison.scala | 34 +- .../dbuild/build/LocalBuildMain.scala | 48 +- .../dbuild/build/LocalBuilderActor.scala | 16 +- .../typesafe/dbuild/build/OptionTask.scala | 9 +- .../typesafe/dbuild/build/SbtBuildMain.scala | 71 +- .../dbuild/build/SimpleBuildActor.scala | 43 +- .../typesafe/dbuild/project/BuildSystem.scala | 6 +- .../dbuild/utils/InterruptibleProcess.scala | 4 +- .../com/typesafe/dbuild/deploy/Deploy.scala | 88 +- .../com/typesafe/dbuild/graph/Graphs.scala | 4 +- .../typesafe/dbuild/graph/SimpleGraph.scala | 2 +- .../com/typesafe/dbuild/hashing/package.scala | 12 +- .../typesafe/dbuild/http/HttpTransfer.scala | 92 +- .../typesafe/dbuild/manifest/ModuleInfo.scala | 14 + .../com/typesafe/dbuild/logging/Logger.scala | 12 +- .../scala-2.12/CirceDerivationCompat.scala | 20 + .../main/scala-3/CirceDerivationCompat.scala | 19 + .../com/typesafe/dbuild/model/Artifacts.scala | 49 ++ .../typesafe/dbuild/model/BuildOutcome.scala | 20 +- .../com/typesafe/dbuild/model/Config.scala | 816 ++++++++++-------- .../com/typesafe/dbuild/model/Extracted.scala | 30 +- .../typesafe/dbuild/model/GraphSupport.scala | 8 +- .../dbuild/model/RepeatableBuild.scala | 45 +- .../dbuild/model/TemplateFormatter.scala | 42 +- .../com/typesafe/dbuild/model/Utils.scala | 138 +-- .../dbuild/model/DBuildConfigSpec.scala | 8 +- .../dbuild/model/RepeatableBuildSpec.scala | 13 +- .../typesafe/dbuild/plugin/DBuildKeys.scala | 4 +- .../typesafe/dbuild/plugin/DBuildRunner.scala | 247 +++--- .../dbuild/plugin/DependencyAnalysis.scala | 58 +- .../typesafe/dbuild/plugin/PluginFixins.scala | 9 +- .../dbuild/project/build/BuildDirs.scala | 9 +- .../project/build/LocalBuildRunner.scala | 19 +- .../project/dependencies/Extractor.scala | 32 +- project/Dependencies.scala | 60 +- project/LocalPlugin.scala | 14 + project/SbtSupport.scala | 8 +- project/packaging.scala | 16 +- .../dbuild/repo/core/CachedRepository.scala | 20 +- .../dbuild/repo/core/GlobalDirs.scala | 13 +- .../typesafe/dbuild/repo/core/Helper.scala | 21 +- .../core/OfflineLocalCacheRepository.scala | 2 +- .../dbuild/repo/core/ProjectRepoMain.scala | 11 +- .../dbuild/repo/core/Repository.scala | 7 +- .../support/sbt/SbtExtractionSpec.scala | 8 +- .../dbuild/support/sbt/SbtTestHarness.scala | 2 +- .../com/typesafe/dbuild/support/SbtUtil.scala | 8 +- .../support/aether/AetherBuildSystem.scala | 95 +- .../support/aether/ProjectResolver.scala | 14 +- .../assemble/AssembleBuildSystem.scala | 65 +- .../dbuild/support/ivy/IvyBuildSystem.scala | 60 +- .../dbuild/support/ivy/IvyMachinery.scala | 66 +- .../dbuild/support/nil/ProjectResolver.scala | 12 +- .../support/sbt/DependencyExtractor.scala | 31 +- .../dbuild/support/sbt/Repositories.scala | 4 +- .../dbuild/support/sbt/SbtBuildRunner.scala | 38 +- .../dbuild/support/sbt/SbtBuildSystem.scala | 24 +- .../dbuild/support/sbt/SbtRunner.scala | 47 +- .../support/scala/ScalaBuildSystem.scala | 62 +- .../dbuild/support/svn/ProjectResolver.scala | 12 +- .../com/typesafe/dbuild/support/svn/Svn.scala | 8 +- .../dbuild/support/test/ProjectResolver.scala | 14 +- .../com/typesafe/dbuild/support/git/Git.scala | 32 +- .../dbuild/support/git/ProjectResolver.scala | 12 +- 77 files changed, 1992 insertions(+), 1633 deletions(-) delete mode 100644 adapter/src/main/scala-2.10/Adapter.scala create mode 100644 adapter/src/main/scala-3/Adapter.scala create mode 100644 adapter/src/main/scala/AdapterCommon.scala create mode 100644 adapter/src/main/scala/DbuildHack.scala create mode 100644 metadata/src/main/scala-2.12/CirceDerivationCompat.scala create mode 100644 metadata/src/main/scala-3/CirceDerivationCompat.scala create mode 100644 project/LocalPlugin.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac80d01df..e6be4fd25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,8 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-java@v5 with: - distribution: zulu - java-version: 8 + distribution: temurin + java-version: 17 cache: sbt - uses: sbt/setup-sbt@v1 - name: Test diff --git a/.gitignore b/.gitignore index 2f7703904..66e5b3dc4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target-* __pycache__/ docs/src/sphinx/version.py .bsp +.claude .devcontainer .metals .vscode diff --git a/actorLogging/src/main/scala/com/typesafe/dbuild/logging/ActorLogging.scala b/actorLogging/src/main/scala/com/typesafe/dbuild/logging/ActorLogging.scala index d1c6134ad..8e22d1fee 100644 --- a/actorLogging/src/main/scala/com/typesafe/dbuild/logging/ActorLogging.scala +++ b/actorLogging/src/main/scala/com/typesafe/dbuild/logging/ActorLogging.scala @@ -1,18 +1,18 @@ package com.typesafe.dbuild.logging -import akka.actor.{ ActorRef, Actor, Props, PoisonPill, Terminated } +import org.apache.pekko.actor.{ ActorRef, Actor, Props, PoisonPill, Terminated } +import sbt.internal.util.StackTrace import sbt.io.IO -import sbt.io.Path._ -import sbt.io.syntax._ +import sbt.io.Path.* +import sbt.io.syntax.* import sbt.util.{ Level, LogEvent, Log, Trace, ControlEvent } -import sbt.internal.util.StackTrace /** A command to the logging system to log a given event into a particular path. */ case class LogCmd(path: String, evt: LogEvent, projName: String) /** A logger that sends LogCmds to an actor rather than doing anything directly. */ class ActorLogger(nested: ActorRef, path: String = "root", val projName: String = "") extends Logger { -import Level._ +import Level.* // TODO - Keep a reference of what's enabled locally... def newNestedLogger(name: String, projName: String = ""): Logger = { new ActorLogger(nested, path + "/" + name, projName) @@ -163,10 +163,10 @@ class ChainedLoggerSupervisorActor extends Actor { val logger = context actorOf p context.watch(logger) loggers = logger +: loggers - sender ! logger + sender() ! logger case l: LogCmd => loggers foreach (_ forward l) case "exit" => - terminationSender = sender + terminationSender = sender() loggers.foreach { _ ! "exit" } case Terminated(p) => if (!(loggers.contains(p))) diff --git a/actorProj/src/main/scala/com/typesafe/dbuild/project/build/BuildRunnerActor.scala b/actorProj/src/main/scala/com/typesafe/dbuild/project/build/BuildRunnerActor.scala index 9976e7e50..c47975652 100644 --- a/actorProj/src/main/scala/com/typesafe/dbuild/project/build/BuildRunnerActor.scala +++ b/actorProj/src/main/scala/com/typesafe/dbuild/project/build/BuildRunnerActor.scala @@ -1,32 +1,32 @@ package com.typesafe.dbuild.project.build -import com.typesafe.dbuild.model._ import com.typesafe.dbuild.logging.Logger -import akka.actor.{ ActorRef, Actor, Props, PoisonPill, Terminated } -import akka.util.Timeout -import scala.concurrent.Await -import scala.concurrent.duration._ +import Logger.prepareLogMsg +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.project.cleanup.Recycling.* import com.typesafe.dbuild.project.resolve.ProjectResolver -import java.io.File -import com.typesafe.dbuild.repo.core._ -import sbt.io.{ IO, DirectoryFilter } -import sbt.io.Path._ -import sbt.io.syntax._ -import com.typesafe.dbuild.project.cleanup.Recycling._ -import com.typesafe.dbuild.repo.core.GlobalDirs.buildDir import com.typesafe.dbuild.project.{ BuildSystem, BuildData } +import com.typesafe.dbuild.repo.core.* +import com.typesafe.dbuild.repo.core.GlobalDirs.buildDir import com.typesafe.dbuild.utils.TrackedProcessBuilder -import akka.pattern.ask +import java.io.File import java.util.concurrent.TimeoutException +import org.apache.pekko.actor.{ ActorRef, Actor, Props, PoisonPill, Terminated } +import org.apache.pekko.pattern.ask +import org.apache.pekko.util.Timeout +import sbt.io.Path.* +import sbt.io.syntax.* +import sbt.io.{ IO, DirectoryFilter } +import scala.concurrent.Await +import scala.concurrent.duration.* import scala.util.{Success,Failure} -import Logger.prepareLogMsg case class RunBuild(build: RepeatableProjectBuild, outProjects: Seq[Project], children: Seq[BuildOutcome], buildData: BuildData) class CleaningBuildActor extends Actor { def receive = { case target: File => - IO.delete(buildDir(target).*(DirectoryFilter).get.filter(markedForDeletion)) + IO.delete(buildDir(target).*(DirectoryFilter).get().filter(markedForDeletion)) self ! PoisonPill } } @@ -37,7 +37,7 @@ class BuildRunnerActor(builder: LocalBuildRunner, target: File, exp: CleanupExpi override def preStart() = { // Cleanup works in two stages; see ExtractorActor for details. // Note that cleanup is performed independently for the extraction and build directories - buildDir(target).*(DirectoryFilter).get.filter(upForDeletion(_, exp)).foreach(prepareForDeletion) + buildDir(target).*(DirectoryFilter).get().filter(upForDeletion(_, exp)).foreach(prepareForDeletion) // spawn the cleaning actor context.actorOf(Props(new CleaningBuildActor)) ! target } @@ -45,7 +45,7 @@ class BuildRunnerActor(builder: LocalBuildRunner, target: File, exp: CleanupExpi def receive = { case RunBuild(build, outProjects, children, buildData@BuildData(log, _)) => log info ("--== Building %s ==--" format (build.config.name)) - sender ! (try { + sender() ! (try { builder.checkCacheThenBuild(target, build, tracker, outProjects, children, buildData) } catch { case t:Throwable => @@ -61,7 +61,7 @@ class TimedBuildRunnerActor(builder: LocalBuildRunner, target: File, val realBuilder = context.actorOf(Props(new BuildRunnerActor(builder, target, exp, tracker))) def receive = { case msg@RunBuild(build, outProjects, children, buildData@BuildData(log, _)) => - val originalSender = sender // need to copy, as we we'll use it later in a future (andThen) + val originalSender = sender() // need to copy, as we we'll use it later in a future (andThen) tracker.reset() val future1 = (realBuilder ? msg)(Timeout(buildDuration)) val future2 = future1.andThen { @@ -80,7 +80,7 @@ class TimedBuildRunnerActor(builder: LocalBuildRunner, target: File, log.error(timeoutMsg) originalSender ! new BuildFailed(build.config.name, children, timeoutMsg) with TimedOut case _ => - originalSender ! akka.actor.Status.Failure(e) + originalSender ! org.apache.pekko.actor.Status.Failure(e) } } (scala.concurrent.ExecutionContext.Implicits.global) diff --git a/actorProj/src/main/scala/com/typesafe/dbuild/project/dependencies/ExtractorActor.scala b/actorProj/src/main/scala/com/typesafe/dbuild/project/dependencies/ExtractorActor.scala index 427922674..199f51bdb 100644 --- a/actorProj/src/main/scala/com/typesafe/dbuild/project/dependencies/ExtractorActor.scala +++ b/actorProj/src/main/scala/com/typesafe/dbuild/project/dependencies/ExtractorActor.scala @@ -1,35 +1,35 @@ package com.typesafe.dbuild.project.dependencies -import akka.actor.{ ActorRef, Actor, Props, PoisonPill, Terminated } -import akka.util.Timeout -import akka.pattern.ask -import scala.concurrent.Await -import scala.concurrent.duration._ -import com.typesafe.dbuild.model.{ ProjectBuildConfig, ExtractionConfig, ExtractionFailed, TimedOut } -import _root_.java.io.File -import sbt.io.Path._ -import sbt.io.syntax._ import ExtractionDirs.projectExtractionDir -import com.typesafe.dbuild.repo.core.GlobalDirs.extractionDir -import com.typesafe.dbuild.model.CleanupExpirations -import com.typesafe.dbuild.project.cleanup.Recycling._ -import sbt.io.{ IO, DirectoryFilter } +import _root_.java.io.File import com.typesafe.dbuild.logging.Logger import Logger.prepareLogMsg -import scala.util.{Success,Failure} -import java.util.concurrent.TimeoutException +import com.typesafe.dbuild.model.CleanupExpirations +import com.typesafe.dbuild.model.{ ProjectBuildConfig, ExtractionConfig, ExtractionFailed, TimedOut } +import com.typesafe.dbuild.project.cleanup.Recycling.* +import com.typesafe.dbuild.repo.core.GlobalDirs.extractionDir import com.typesafe.dbuild.utils.TrackedProcessBuilder +import java.util.concurrent.TimeoutException +import org.apache.pekko.actor.{ ActorRef, Actor, Props, PoisonPill, Terminated } +import org.apache.pekko.pattern.ask +import org.apache.pekko.util.Timeout +import sbt.io.Path.* +import sbt.io.syntax.* +import sbt.io.{ IO, DirectoryFilter } +import scala.concurrent.Await +import scala.concurrent.duration.* +import scala.util.{Success,Failure} case class ExtractBuildDependencies(config: ExtractionConfig, uuidDir: String, log: Logger, debug: Boolean) class CleaningExtractionActor extends Actor { def receive = { case target: File => - extractionDir(target).*(DirectoryFilter).get.foreach { d1 => + extractionDir(target).*(DirectoryFilter).get().foreach { d1 => if (markedForDeletion(d1)) IO.delete(d1) else - IO.delete(projectExtractionDir(d1).*(DirectoryFilter).get.filter(markedForDeletion)) + IO.delete(projectExtractionDir(d1).*(DirectoryFilter).get().filter(markedForDeletion)) } self ! PoisonPill } @@ -54,9 +54,9 @@ class ExtractorActor(e: Extractor, target: File, exp: CleanupExpirations, tracke // There are two levels in the hierarchy, so we mark for deletion first the nested // ones and, if all the content can be deleted, the outer one as well. - extractionDir(target).*(DirectoryFilter).get.foreach { d1 => + extractionDir(target).*(DirectoryFilter).get().foreach { d1 => if (!markedForDeletion(d1)) { // skip if already marked - val candidates = projectExtractionDir(d1).*(DirectoryFilter).get + val candidates = projectExtractionDir(d1).*(DirectoryFilter).get() val (delete, doNotDelete) = candidates.partition(upForDeletion(_, exp)) if (doNotDelete.isEmpty) // everything can be deleted inside this dir, or there is prepareForDeletion(d1) // nothing left, so remove the dir @@ -76,7 +76,7 @@ class ExtractorActor(e: Extractor, target: File, exp: CleanupExpirations, tracke def receive: Receive = { case ExtractBuildDependencies(build, uuidDir, log, debug) => log info ("--== Extracting dependencies for %s ==--" format (build.buildConfig.name)) - sender ! (try { + sender() ! (try { e.extract(extractionDir(target) / uuidDir, build, tracker, log, debug) } catch { case t:Throwable => @@ -92,7 +92,7 @@ class TimedExtractorActor(extractor: Extractor, target: File, val realExtractor = context.actorOf(Props(new ExtractorActor(extractor, target, exp, tracker))) def receive = { case msg@ExtractBuildDependencies(build, uuidDir, log, debug) => - val originalSender = sender // need to copy, as we we'll use it later in a future (andThen) + val originalSender = sender() // need to copy, as we we'll use it later in a future (andThen) tracker.reset() val future1 = (realExtractor ? msg)(Timeout(extractionDuration)) val future2 = future1.andThen { @@ -111,7 +111,7 @@ class TimedExtractorActor(extractor: Extractor, target: File, log.error(timeoutMsg) originalSender ! new ExtractionFailed(build.buildConfig.name, Seq(), timeoutMsg) with TimedOut case _ => - originalSender ! akka.actor.Status.Failure(e) + originalSender ! org.apache.pekko.actor.Status.Failure(e) } } (scala.concurrent.ExecutionContext.Implicits.global) diff --git a/adapter/src/main/scala-2.10/Adapter.scala b/adapter/src/main/scala-2.10/Adapter.scala deleted file mode 100644 index 26363db2a..000000000 --- a/adapter/src/main/scala-2.10/Adapter.scala +++ /dev/null @@ -1,88 +0,0 @@ -import org.apache.ivy.core.module.id.ModuleRevisionId -import org.apache.ivy.core.module.descriptor.ModuleDescriptor - -package sbt.dbuild.hack { -object DbuildHack { - val Load = sbt.Load - val ExceptionCategory = sbt.ExceptionCategory -} -} - -package com.typesafe.dbuild.adapter { -import java.io.File - -object LoggingInterface { - val Level = sbt.Level - type Logger = sbt.Logger - type LogEvent = sbt.LogEvent - type Log = sbt.Log - type Trace = sbt.Trace - val ControlEvent = sbt.ControlEvent - type ControlEvent = sbt.ControlEvent - val StackTrace = sbt.StackTrace - type BasicLogger = sbt.BasicLogger -} - -import LoggingInterface.Level._ -trait StreamLoggerAdapter { - def log(level: Value, message: => String): Unit - def log(label: String, message: String): Unit - def err(s: => String): Unit = log(Error, s) - def out(s: => String): Unit = log(Info.toString, s) -} - -object Adapter { - val IO = sbt.IO - val Path = sbt.Path - type RichFile = sbt.RichFile - def newIvyPaths(baseDirectory: java.io.File, ivyHome: Option[java.io.File]) = - new sbt.IvyPaths(baseDirectory, ivyHome) - type FileFilter = sbt.FileFilter - def toFF = sbt.FileFilter.globFilter _ - val DirectoryFilter = sbt.DirectoryFilter - type ExactFilter = sbt.ExactFilter - type NameFilter = sbt.NameFilter - type FileRepository = sbt.FileRepository - type Logger = sbt.Logger - import Path._ - def allPaths(f:File) = sbt.PathFinder(f).*** - val syntaxio = new {} - type ModuleID = sbt.ModuleID - type Artifact = sbt.Artifact - type CrossVersion = sbt.CrossVersion - type IvyScala = sbt.IvyScala - val keyIvyScala = sbt.Keys.ivyScala - def interProjectResolver(k:Map[ModuleRevisionId, ModuleDescriptor]) = - new sbt.RawRepository(new sbt.ProjectResolver("inter-project", k)) - def moduleWithName(m:ModuleID, n:String) = m.copy(name=n) - def moduleWithRevision(m:ModuleID, r:String) = m.copy(revision=r) - def moduleWithCrossVersion(m:ModuleID, cross:CrossVersion) = m.copy(crossVersion=cross) - def moduleWithExplicitArtifacts(m:ModuleID, ea:Seq[Artifact]) = m.copy(explicitArtifacts=ea) - def moduleWithExtraAttributes(m:ModuleID, ea:Map[String,String]) = m.copy(extraAttributes=ea) - def ivyScalaWithCheckExplicit(i:IvyScala, ce:Boolean) = i.copy(checkExplicit=ce) - def artifactWithClassifier(a:Artifact, cl:Option[String]) = a.copy(classifier=cl) - val crossDisabled = sbt.CrossVersion.Disabled - type crossDisabled = sbt.CrossVersion.Disabled.type - val crossBinary = sbt.CrossVersion.binary - type crossBinary = sbt.CrossVersion.Binary - val crossFull = sbt.CrossVersion.full - type crossFull = sbt.CrossVersion.Full - type ProjectResolver = sbt.ProjectResolver - type ScalaInstance = sbt.ScalaInstance - lazy val ScalaInstance = sbt.ScalaInstance - lazy val Load = sbt.dbuild.hack.DbuildHack.Load - val applyCross: (String, Option[String => String]) => String = - sbt.CrossVersion.applyCross - def defaultID(base: File, prefix: String = "default") = - sbt.Build.defaultID(base, prefix) - - def reapplySettings(newSettings: Seq[sbt.Def.Setting[_]], - structure: sbt.BuildStructure, - log: sbt.Logger)(implicit display: sbt.Show[sbt.Def.ScopedKey[_]]): sbt.BuildStructure = { - Load.reapply(newSettings, structure) - } - - def scalaInstance(libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance = - ScalaInstance(libraryJar, compilerJar, launcher, extraJars:_*) -} -} diff --git a/adapter/src/main/scala-2.12/Adapter.scala b/adapter/src/main/scala-2.12/Adapter.scala index 72ad97721..20c240b32 100644 --- a/adapter/src/main/scala-2.12/Adapter.scala +++ b/adapter/src/main/scala-2.12/Adapter.scala @@ -1,79 +1,34 @@ -import org.apache.ivy.core.module.id.ModuleRevisionId -import org.apache.ivy.core.module.descriptor.ModuleDescriptor - -package sbt.dbuild.hack { -object DbuildHack { - val Load = sbt.internal.Load - val applyCross: (String, Option[String => String]) => String = - sbt.librarymanagement.CrossVersion.applyCross - val defaultID: (java.io.File,String) => String = - sbt.internal.BuildDef.defaultID - val ExceptionCategory = sbt.ExceptionCategory -} -} -package com.typesafe.dbuild.adapter { +package com.typesafe.dbuild.adapter import java.io.File +import sbt.Keys.packagedArtifacts +import sbt.internal.BuildStructure +import sbt.internal.inc.ScalaInstance +import sbt.util.{ Logger, Show } +import sbt.{ Artifact, AttributeKey, Compile, ConfigKey, Def, InputTask, Reference, Scope, Settings, Task } -object LoggingInterface { - val Level = sbt.util.Level - type Logger = sbt.util.Logger - type LogEvent = sbt.util.LogEvent - type Log = sbt.util.Log - type Trace = sbt.util.Trace - val ControlEvent = sbt.util.ControlEvent - type ControlEvent = sbt.util.ControlEvent - val StackTrace = sbt.internal.util.StackTrace - type BasicLogger = sbt.internal.util.BasicLogger -} - -trait StreamLoggerAdapter - -object Adapter { - val IO = sbt.io.IO - val Path = sbt.io.Path - type RichFile = sbt.io.RichFile +object Adapter extends AdapterCommon { def newIvyPaths(baseDirectory: java.io.File, ivyHome: Option[java.io.File]) = sbt.librarymanagement.ivy.IvyPaths(baseDirectory, ivyHome) - type FileFilter = sbt.io.FileFilter - def toFF = sbt.io.FileFilter.globFilter _ - val DirectoryFilter = sbt.io.DirectoryFilter - type ExactFilter = sbt.io.ExactFilter - type NameFilter = sbt.io.NameFilter - type FileRepository = sbt.librarymanagement.FileRepository - type Logger = sbt.util.Logger - def allPaths(f:File) = sbt.io.PathFinder(f).allPaths - val syntaxio = sbt.io.syntax - type ModuleID = sbt.librarymanagement.ModuleID - type Artifact = sbt.librarymanagement.Artifact - type CrossVersion = sbt.librarymanagement.CrossVersion - type IvyScala = sbt.librarymanagement.ScalaModuleInfo - def interProjectResolver(k:Map[ModuleRevisionId, ModuleDescriptor]) = - new sbt.librarymanagement.RawRepository(new sbt.internal.librarymanagement.ProjectResolver("inter-project", k), "inter-project") - val keyIvyScala = sbt.Keys.scalaModuleInfo - def moduleWithName(m:ModuleID, name:String) = m.withName(name) - def moduleWithRevision(m:ModuleID, revision:String) = m.withRevision(revision) - def moduleWithCrossVersion(m:ModuleID, cross:CrossVersion) = m.withCrossVersion(cross) - def moduleWithExplicitArtifacts(m:ModuleID, ea:Seq[Artifact]) = m.withExplicitArtifacts(ea.toVector) - def moduleWithExtraAttributes(m:ModuleID, ea:Map[String,String]) = m.withExtraAttributes(ea) - def ivyScalaWithCheckExplicit(i:IvyScala, ce:Boolean) = i.withCheckExplicit(ce) - def artifactWithClassifier(a:Artifact, cl:Option[String]) = a.withClassifier(cl) - val crossDisabled = sbt.librarymanagement.Disabled() - type crossDisabled = sbt.librarymanagement.Disabled - val crossBinary = sbt.librarymanagement.Binary() - type crossBinary = sbt.librarymanagement.Binary - val crossFull = sbt.librarymanagement.Full() - type crossFull = sbt.librarymanagement.Full - type ProjectResolver = sbt.internal.librarymanagement.ProjectResolver - type ScalaInstance = sbt.internal.inc.ScalaInstance - val ScalaInstance = sbt.internal.inc.ScalaInstance - val Load = sbt.dbuild.hack.DbuildHack.Load - val applyCross = sbt.dbuild.hack.DbuildHack.applyCross - def defaultID(base: File, prefix: String = "default") = - sbt.dbuild.hack.DbuildHack.defaultID(base, prefix) - def reapplySettings(newSettings: Seq[sbt.Def.Setting[_]], - structure: sbt.internal.BuildStructure, - log: sbt.util.Logger)(implicit display: sbt.util.Show[sbt.Def.ScopedKey[_]]): sbt.internal.BuildStructure = { + // On sbt 1.x, Compile/packagedArtifacts is already a Map[Artifact, File]; this setting + // just forwards it, so that shared code can use the same key name on both sbt versions. + val packagedArtifactsSetting: Def.Setting[Task[Map[Artifact, File]]] = + packagedArtifactsAsFiles := (packagedArtifacts in Compile).value + + def rescopeForTest(ref: Reference, conf: ConfigKey): Scope = Scope.ThisScope.in(ref, conf) + def isKeyDefined(data: Settings[Scope], scope: Scope, key: AttributeKey[?]): Boolean = + data.get(scope, key).isDefined + def classifyKey(key: AttributeKey[?]): KeyKind = { + val taskManifest = scala.reflect.ClassManifest.fromClass(classOf[Task[?]]).erasure + val inputTaskManifest = scala.reflect.ClassManifest.fromClass(classOf[InputTask[?]]).erasure + if (key.manifest.erasure == taskManifest) TaskKeyKind + else if (key.manifest.erasure == inputTaskManifest) InputTaskKeyKind + else OtherKeyKind + } + + def reapplySettings(newSettings: Seq[Def.Setting[?]], + structure: BuildStructure, + log: Logger)(implicit display: Show[Def.ScopedKey[?]]): BuildStructure = { val ru = scala.reflect.runtime.universe val rm = ru.runtimeMirror(getClass.getClassLoader) val im = rm.reflect(Load) @@ -87,65 +42,15 @@ object Adapter { reapply(newSettings, structure, log, display) else reapply(newSettings, structure, display) - ).asInstanceOf[sbt.internal.BuildStructure] + ).asInstanceOf[BuildStructure] } -// These bits are inappropriately copied from various versions of zinc; some have been -// removed and some made private, but we need them. -// See: internal/zinc-classpath/src/main/scala/sbt/internal/inc/ScalaInstance.scala - - import java.net.{ URL, URLClassLoader } - /** Runtime exception representing a failure when finding a `ScalaInstance`. */ class InvalidScalaInstance(message: String, cause: Throwable) extends RuntimeException(message, cause) - /** The prefix being used for Scala artifacts name creation. */ - val VersionPrefix = "version " - - private def slowActualVersion(scalaLoader: ClassLoader)(label: String) = { - val scalaVersion = { - try { - // Get scala version from the `Properties` file in Scalac - Class - .forName("scala.tools.nsc.Properties", true, scalaLoader) - .getMethod("versionString") - .invoke(null) - .toString - } catch { - case cause: Exception => - val msg = s"Scala instance doesn't exist or is invalid: $label" - throw new InvalidScalaInstance(msg, cause) - } - } - - if (scalaVersion.startsWith(VersionPrefix)) - scalaVersion.substring(VersionPrefix.length) - else scalaVersion - } - - private def fastActualVersion(scalaLoader: ClassLoader): String = { - val stream = scalaLoader.getResourceAsStream("compiler.properties") - try { - val props = new java.util.Properties - props.load(stream) - props.getProperty("version.number") - } finally stream.close() - } - - /** Gets the version of Scala in the compiler.properties file from the loader.*/ - private def actualVersion(scalaLoader: ClassLoader)(label: String) = { - try fastActualVersion(scalaLoader) - catch { case e: Exception => slowActualVersion(scalaLoader)(label) } - } - - private def scalaLoader(launcher: xsbti.Launcher): Seq[File] => ClassLoader = { jars => - import java.net.{ URL, URLClassLoader } - new URLClassLoader( - jars.map(_.toURI.toURL).toArray[URL], - launcher.topLoader - ) - } + protected def invalidScalaInstance(message: String, cause: Throwable): RuntimeException = + new InvalidScalaInstance(message, cause) private def scalaInstanceHelper(libraryJar: File, compilerJar: File, extraJars: File*)(classLoader: List[File] => ClassLoader): ScalaInstance = { @@ -156,6 +61,4 @@ object Adapter { def scalaInstance(libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance = scalaInstanceHelper(libraryJar, compilerJar, extraJars: _*)(scalaLoader(launcher)) - -} } diff --git a/adapter/src/main/scala-3/Adapter.scala b/adapter/src/main/scala-3/Adapter.scala new file mode 100644 index 000000000..9a9a1ea94 --- /dev/null +++ b/adapter/src/main/scala-3/Adapter.scala @@ -0,0 +1,59 @@ +package com.typesafe.dbuild.adapter +import java.io.File +import sbt.Keys.{ fileConverter, packagedArtifacts } +import sbt.internal.BuildStructure +import sbt.internal.inc.{ InvalidScalaInstance, ScalaInstance } +import sbt.internal.util.KeyTag +import sbt.librarymanagement.IvyPaths +import sbt.util.{ Logger, Show } +import sbt.{ *, given } +import sbtcompat.PluginCompat.virtualFileRefToFile + +object Adapter extends AdapterCommon: + def newIvyPaths(baseDirectory: File, ivyHome: Option[File]) = + IvyPaths(baseDirectory.getAbsolutePath, ivyHome.map(_.getAbsolutePath)) + + // sbt 2.x's Compile/packagedArtifacts returns Map[Artifact, HashedVirtualFileRef] rather than + // Map[Artifact, File]; this setting re-materializes the plain File map, using Keys.fileConverter + // to resolve each virtual file reference to a real path. + val packagedArtifactsSetting: Def.Setting[Task[Map[Artifact, File]]] = { + packagedArtifactsAsFiles := Def.uncached { + val conv = fileConverter.value + val arts = (Compile / packagedArtifacts).value + arts.map { case (a, ref) => + a -> virtualFileRefToFile(ref)(using conv) + } + } + } + + def rescopeForTest(ref: Reference, conf: ConfigKey): Scope = + Scope.ThisScope.rescope(ref).rescope(conf) + def isKeyDefined(data: Def.Settings, scope: Scope, key: AttributeKey[?]): Boolean = + data.get(Def.ScopedKey(scope, key)).isDefined + def classifyKey(key: AttributeKey[?]): KeyKind = + key.tag match + case _: KeyTag.Task[?] => TaskKeyKind + case _: KeyTag.InputTask[?] => InputTaskKeyKind + case _ => OtherKeyKind + + def reapplySettings(newSettings: Seq[Def.Setting[?]], + structure: BuildStructure, + log: Logger)(implicit display: Show[Def.ScopedKey[?]]): BuildStructure = + // sbt 2.x's Load.reapply() went back to a plain two-arg signature + // (plus an implicit Show), so no version-detecting reflection is needed here. + Load.reapply(newSettings, structure) + + protected def invalidScalaInstance(message: String, cause: Throwable): RuntimeException = + new InvalidScalaInstance(message, cause) + + private def scalaInstanceHelper(libraryJar: File, compilerJar: File, extraJars: File*)(classLoader: List[File] => ClassLoader): ScalaInstance = + val loader = classLoader(libraryJar :: compilerJar :: extraJars.toList) + val version = actualVersion(loader)(" (library jar " + libraryJar.getAbsolutePath + ")") + // sbt 2.x's ScalaInstance separates the compiler-only/library-only classloaders + // and takes plural jar arrays; dbuild only ever has a single shared loader here. + new ScalaInstance(version, loader, loader, loader, + Array(libraryJar), Array(compilerJar), (libraryJar +: compilerJar +: extraJars).toArray, None) + + def scalaInstance(libraryJar: File, compilerJar: File, launcher: xsbti.Launcher, extraJars: File*): ScalaInstance = + scalaInstanceHelper(libraryJar, compilerJar, extraJars*)(scalaLoader(launcher)) +end Adapter diff --git a/adapter/src/main/scala/AdapterCommon.scala b/adapter/src/main/scala/AdapterCommon.scala new file mode 100644 index 000000000..5d01a8bea --- /dev/null +++ b/adapter/src/main/scala/AdapterCommon.scala @@ -0,0 +1,127 @@ +package com.typesafe.dbuild.adapter + +import java.io.File +import org.apache.ivy.core.module.descriptor.ModuleDescriptor +import org.apache.ivy.core.module.id.ModuleRevisionId +import sbt.dbuild.hack.DbuildHack +import sbt.internal.librarymanagement.{ ProjectResolver => IvyProjectResolver } +import sbt.io.PathFinder +import sbt.librarymanagement.RawRepository +import sbt.{ Artifact, TaskKey } +import xsbti.Launcher + +// The members below are identical across the sbt-1.x (scala-2.12) and sbt-2.x (scala-3) +// Adapter variants, so they live here, in the source dir shared by both cross-builds, +// rather than being duplicated in each of adapter/src/main/scala-{2.12,3}/Adapter.scala. +// +// Note: plain re-exports of stable sbt symbols (val IO = sbt.io.IO, type ModuleID = +// sbt.librarymanagement.ModuleID, etc.) have been removed from this trait entirely; +// callers should import those directly from sbt (e.g. `import sbt.io.IO`) instead of +// going through Adapter. Only genuine helpers and the dbuild-specific hack-package +// forwarders remain here. + +/** + * Members of the `Adapter` object that are identical whether the target is sbt 1.x + * (Scala 2.12) or sbt 2.x (Scala 3). The per-Scala-version `Adapter` objects extend + * this trait and add only the members that genuinely differ between the two sbt APIs. + */ +trait AdapterCommon { + def allPaths(f:File) = PathFinder(f).allPaths + + def interProjectResolver(k:Map[ModuleRevisionId, ModuleDescriptor]) = + new RawRepository(new IvyProjectResolver("inter-project", k), "inter-project") + + // Coursier-native counterpart to interProjectResolver(), above: sbt has used coursier + // (not Ivy) as its default dependency-resolution engine since sbt 1.3, on both the sbt-1.x + // and sbt-2.x lines, and its coursier integration exposes this as sbt.Keys.csrInterProjectDependencies + // (a TaskKey[Seq[lmcoursier.definitions.Project]]) rather than as a Resolver. Unlike the + // Ivy-based resolver, coursier's engine needs no full dependency graph for these entries -- + // just enough identity (module coordinates + version) for it to recognize the module as + // already known, rather than going out to fetch it. + def csrInterProjectDependencies(k: Map[ModuleRevisionId, ModuleDescriptor]): Seq[lmcoursier.definitions.Project] = + k.keys.map { mr => + lmcoursier.definitions.Project( + lmcoursier.definitions.Module( + lmcoursier.definitions.Organization(mr.getOrganisation), + lmcoursier.definitions.ModuleName(mr.getName), + Map.empty), + mr.getRevision, + Seq.empty, + Map.empty, + Seq.empty, + None, + Seq.empty, + lmcoursier.definitions.Info("", "", Seq.empty, Seq.empty, None) + ) + }.toSeq + + val Load = DbuildHack.Load + val applyCross = DbuildHack.applyCross + def defaultID(base: File, prefix: String = "default") = + DbuildHack.defaultID(base, prefix) + val projectDescriptorsKey = DbuildHack.projectDescriptors + + // sbt 1.x/2.x disagree on the Compile/packagedArtifacts value type (File vs a virtual + // file reference); this key is bound to a version-specific setting (see each Adapter + // variant's packagedArtifactsSetting) that normalizes it back to a plain File map. + val packagedArtifactsAsFiles: TaskKey[Map[Artifact, File]] = + TaskKey[Map[Artifact, File]]("dbuild-packaged-artifacts-as-files") + + // The following bits abstract over the sbt-1.x-vs-sbt-2.x differences in the low-level + // Scope/AttributeKey machinery, used to dynamically look up and run an arbitrary named + // task/input-task (see DBuildRunner.buildStuff's doTestTask). + sealed trait KeyKind + case object TaskKeyKind extends KeyKind + case object InputTaskKeyKind extends KeyKind + case object OtherKeyKind extends KeyKind + + // These bits are inappropriately copied from various versions of zinc; some have been + // removed and some made private, but we need them. + // See: internal/zinc-classpath/src/main/scala/sbt/internal/inc/ScalaInstance.scala + + /** The prefix being used for Scala artifacts name creation. */ + val VersionPrefix = "version " + + /** Builds the version-appropriate "invalid Scala instance" exception. */ + protected def invalidScalaInstance(message: String, cause: Throwable): RuntimeException + + /** Gets the version of Scala in the compiler.properties file from the loader.*/ + protected def actualVersion(scalaLoader: ClassLoader)(label: String): String = { + // Fast path: read the version straight from compiler.properties. + try { + val stream = scalaLoader.getResourceAsStream("compiler.properties") + try { + val props = new java.util.Properties + props.load(stream) + props.getProperty("version.number") + } finally stream.close() + } catch { + // Slow path: fall back to invoking scala.tools.nsc.Properties.versionString reflectively. + case e: Exception => + val scalaVersion = { + try { + Class + .forName("scala.tools.nsc.Properties", true, scalaLoader) + .getMethod("versionString") + .invoke(null) + .toString + } catch { + case cause: Exception => + val msg = s"Scala instance doesn't exist or is invalid: $label" + throw invalidScalaInstance(msg, cause) + } + } + if (scalaVersion.startsWith(VersionPrefix)) + scalaVersion.substring(VersionPrefix.length) + else scalaVersion + } + } + + protected def scalaLoader(launcher: Launcher): Seq[File] => ClassLoader = { jars => + import java.net.{ URL, URLClassLoader } + new URLClassLoader( + jars.map(_.toURI.toURL).toArray[URL], + launcher.topLoader + ) + } +} diff --git a/adapter/src/main/scala/DbuildHack.scala b/adapter/src/main/scala/DbuildHack.scala new file mode 100644 index 000000000..f58594561 --- /dev/null +++ b/adapter/src/main/scala/DbuildHack.scala @@ -0,0 +1,17 @@ +package sbt.dbuild.hack + +import sbt.internal.{ BuildDef, Load => SbtLoad } +import sbt.librarymanagement.CrossVersion +import sbt.{ ExceptionCategory => SbtExceptionCategory, Keys } + +object DbuildHack { + val Load = SbtLoad + val applyCross: (String, Option[String => String]) => String = + CrossVersion.applyCross + val defaultID: (java.io.File,String) => String = + BuildDef.defaultID + val ExceptionCategory = SbtExceptionCategory + // projectDescriptors is private[sbt] in sbt 2.x; this forwarder (living inside a + // subpackage of sbt) re-exposes it to the dbuild adapter. + val projectDescriptors = Keys.projectDescriptors +} diff --git a/build.sbt b/build.sbt index b244b12aa..c9bab34a4 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import SbtSupport._ def MyVersion: String = "0.9.20" -ThisBuild / scalaVersion := scala212 +ThisBuild / scalaVersion := scala3 // keep Maven Central happy ThisBuild / developers := List( @@ -45,15 +45,15 @@ ThisBuild / version := MyVersion ThisBuild / resolvers += Resolver.typesafeIvyRepo("releases") ThisBuild / resolvers += "Typesafe Repository" at "https://repo.typesafe.com/typesafe/releases/" -def skip210 = Seq( - (compile / skip) := scalaVersion.value.startsWith("2.10"), +def skip212 = Seq( + (compile / skip) := scalaVersion.value.startsWith("2.12"), publish := Def.taskDyn { val p = publish.taskValue - if (scalaVersion.value.startsWith("2.10")) Def.task {} else Def.task(p.value) + if (scalaVersion.value.startsWith("2.12")) Def.task {} else Def.task(p.value) }.value, publishLocal := Def.taskDyn { val p = publishLocal.taskValue - if (scalaVersion.value.startsWith("2.10")) Def.task {} else Def.task(p.value) + if (scalaVersion.value.startsWith("2.12")) Def.task {} else Def.task(p.value) }.value, Compile / doc / sources := { val theSources = (Compile / doc / sources).value @@ -61,22 +61,36 @@ def skip210 = Seq( } ) +// Scala-3 dottydoc chokes on lmcoursier's dataclass-annotated definitions (e.g. Project) +// when documenting a public member whose signature mentions them (readTasty fails with +// "undefined: new dataclass.data"); skip doc generation on the Scala 3 pass for projects +// that expose such signatures, without otherwise skipping their compile/publish tasks. +def skipDoc3 = Seq( + Compile / doc / sources := { + val theSources = (Compile / doc / sources).value + if (scalaVersion.value.startsWith("3")) List() else theSources + } +) + def crossBuildForPlugins = - crossScalaVersions := Vector(scala210, scala212) + crossScalaVersions := Vector(scala212, scala3) def selectSbtVersion = (pluginCrossBuild / sbtVersion) := { scalaBinaryVersion.value match { - case "2.10" => "0.13.18" case "2.12" => "1.13.0" - case _ => "2.0.7" + case "3" => "2.0.7" + case v => sys.error("Unexpected Scala binary version: " + v) } } def selectScalaVersion = scalaVersion := { - val sb = (pluginCrossBuild / sbtVersion).value - if (sb.startsWith("0.13")) "2.10.7" else "2.12.21" + (pluginCrossBuild / sbtVersion).value match { + case "1.13.0" => scala212 + case "2.0.7" => scala3 + case v => sys.error("Unexpected sbt version: " + v) + } } lazy val root = (project in file(".")) @@ -88,7 +102,7 @@ lazy val root = (project in file(".")) publishLocal / skip := true, crossScalaVersions := Nil, ) - .settings(crossSbtVersions := Seq("0.13.18", "1.13.0"), selectScalaVersion) + .settings(crossSbtVersions := Seq("1.13.0", "2.0.7"), selectScalaVersion) // This subproject only has dynamically // generated source files, used to adapt @@ -97,9 +111,11 @@ lazy val adapter = project .dependsOnRemote(specs2 _, jline) .dependsOnSbtProvided(sbtLogging, sbtIo, sbtLauncherInt, sbtIvy, sbtSbt) .dependsOnSbtProvidedOpt(zincIf212 _) + .dependsOnSbt(sbt2Compat) .settings( crossBuildForPlugins, selectSbtVersion, + skipDoc3, Compile / sourceGenerators += task { val dir = (Compile / sourceManaged).value val fileName = "Default.scala" @@ -141,6 +157,7 @@ lazy val hashing = project lazy val indexmeta = project .dependsOnRemote(specs2 _, jline) + .dependsOnRemote(circeCore, circeGeneric) .settings( crossBuildForPlugins, selectSbtVersion, @@ -161,14 +178,15 @@ lazy val actorLogging = project .dependsOnRemote(akkaActor _) .dependsOnSbtProvided(sbtLogging, sbtIo, sbtLauncherInt) .settings( - skip210, + crossBuildForPlugins, + skip212, selectSbtVersion, ) lazy val metadata = project .dependsOnRemote(specs2 _, jline) .dependsOn(graph, hashing, indexmeta, deploy) - .dependsOnRemote(jackson, typesafeConfig, commonsLang, jacks) + .dependsOnRemote(circeCore, circeGeneric, circeGenericExtras _, circeParser, typesafeConfig, commonsLang) .settings( crossBuildForPlugins, selectSbtVersion, @@ -189,7 +207,7 @@ lazy val repo = project lazy val http = project .dependsOn(adapter) .dependsOnRemote(specs2 _, jline) - .dependsOnRemote(dispatch _) + .dependsOnRemote(gigahorse) .dependsOnSbtProvided(sbtIo, sbtIvy, sbtSbt) .settings( crossBuildForPlugins, @@ -224,7 +242,8 @@ lazy val actorProj = project .dependsOn(core, actorLogging, proj) .dependsOnSbtProvided(sbtIo, sbtIvy) .settings( - skip210, + crossBuildForPlugins, + skip212, selectSbtVersion, scalaXmlAlways, ) @@ -261,7 +280,8 @@ lazy val supportGit = project .dependsOnRemote(mvnEmbedder, mvnWagon, javaMail, jgit) .dependsOnSbtProvided(sbtLauncherInt, sbtIvy) .settings( - skip210, + crossBuildForPlugins, + skip212, selectSbtVersion, scalaXmlAlways, ) @@ -277,6 +297,7 @@ lazy val plugin = project crossBuildForPlugins, selectSbtVersion, scalaXmlAlways, + skipDoc3, ) lazy val dist = (project in file("dist")) @@ -284,7 +305,8 @@ lazy val dist = (project in file("dist")) .enablePlugins(UniversalPlugin) .settings(Packaging.settings(build,repo):_*) .settings( - skip210, + crossBuildForPlugins, + skip212, selectSbtVersion, scalaXmlAlways, ) @@ -292,7 +314,7 @@ lazy val dist = (project in file("dist")) lazy val deploy = project .dependsOn(adapter, http) .dependsOnRemote(specs2 _, jline) - .dependsOnRemote(jackson, typesafeConfig, commonsLang, aws, uriutil, commonsIO, jsch, jacks) + .dependsOnRemote(circeCore, circeGeneric, circeParser, typesafeConfig, commonsLang, aws, uriutil, commonsIO, jsch) .dependsOnSbtProvided(sbtLogging, sbtIo, sbtSbt) .settings( crossBuildForPlugins, @@ -305,13 +327,17 @@ lazy val build = project .configs(IntegrationTest) .dependsOn(actorProj, support, supportGit, repo, metadata, deploy, proj) .dependsOnRemote(aws, uriutil, jsch, oro, scallop, commonsLang) - .dependsOnRemote(gpgLibIf210 _) .dependsOnSbt(sbtLauncherInt, sbtLogging, sbtIo, sbtIvy, sbtSbt) .settings(Defaults.itSettings) .settings( - skip210, + crossBuildForPlugins, + skip212, selectSbtVersion, scalaXmlAlways, + // On the Scala 3 pass, scala3-compiler_3 itself depends on an older compiler-interface + // (1.10.7) than the rest of sbt 2.0.7's own zinc/librarymanagement modules (2.0.4); + // both are early-semver compatible, so just take the newer one instead of erroring out. + libraryDependencySchemes += "org.scala-sbt" % "compiler-interface" % VersionScheme.Always, SbtSupport.settings, // We hook the testLoader of it to make sure all the it tasks have a legit sbt plugin to use. // Technically, this just pushes every project. We could outline just the plugin itself, but for now @@ -321,7 +347,6 @@ lazy val build = project (IntegrationTest / testLoader).value }, IntegrationTest / parallelExecution := false, - scalaXmlAlways, ) lazy val docs = project @@ -352,7 +377,7 @@ lazy val sbtLauncherProj = (project in file("sbt-launcher")) .dependsOnRemote(specs2 _, jline) .settings( autoScalaLibrary := false, - libraryDependencies += ("org.scala-sbt" % "sbt-launch" % "1.6.2").intransitive, + libraryDependencies += ("org.scala-sbt" % "sbt-launch" % "2.0.7").intransitive, publish / skip := true, publishLocal / skip := true, ) diff --git a/build/src/it/scala/com/typesafe/dbuild/build/RewireSpec.scala b/build/src/it/scala/com/typesafe/dbuild/build/RewireSpec.scala index 561eb9baf..c1acd8cc6 100644 --- a/build/src/it/scala/com/typesafe/dbuild/build/RewireSpec.scala +++ b/build/src/it/scala/com/typesafe/dbuild/build/RewireSpec.scala @@ -1,83 +1,111 @@ package com.typesafe.dbuild.build +import com.typesafe.config.ConfigFactory import com.typesafe.dbuild.logging.{ConsoleLogger, Logger} -import com.typesafe.dbuild.model._ +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.DBuildConfiguration +import com.typesafe.dbuild.model.Utils.readValueT import com.typesafe.dbuild.project.build.LocalBuildRunner import com.typesafe.dbuild.project.{ BuildSystem, BuildData } -import org.specs2.mutable.Specification -import java.io.File -import com.typesafe.config.ConfigFactory -import com.typesafe.dbuild.model.Utils.readValueT -import com.typesafe.dbuild.model.DBuildConfiguration import com.typesafe.dbuild.support.sbt.Repositories +import java.io.File +import org.specs2.mutable.Specification // TDOO - Because this requires the sbt plugin to be published, we have to publsh locally // before we can run integration tests. object RewireSpec extends Specification { + sequential + "dbuild" should { - "inject into the meta-meta-build" in { - sbt.IO.withTemporaryDirectory { projectDir => + "inject into the meta-meta-build (sbt 1.x)" in { + injectIntoMetaMetaBuild(projectName = "InjectionTest1x", sbtVersion = "1.13.0", extractionVersion = "2.12.21") + } + "inject into the meta-meta-build (sbt 2.x)" in { + // "standard" (rather than an explicit "3.8.4") avoids a genuine dbuild gap: the + // extraction-side Scala-version override (DependencyAnalysis.fixExtractionScalaVersion2) + // only rewrites Keys.scalaVersion and lets sbt's own legacy compiler-fetch fallback run, + // which isn't Scala-3-aware and looks for the old "scala-compiler" module instead of + // "scala3-compiler_3". sbt 2.0.7 already defaults to Scala 3.8.4 on its own, so no + // override is needed here anyway. + injectIntoMetaMetaBuild(projectName = "InjectionTest2x", sbtVersion = "2.0.7", extractionVersion = "standard") + } + } + + // Runs the same "meta-meta-build" injection test against whichever (sbt-version, + // extraction-version) pair is given, so the same scenario can be exercised against + // both the sbt 1.x and the sbt 2.x plugin variants. Each variant uses its own distinct + // project name, so the two runs (which specs2 may execute concurrently) don't collide + // on the same extraction/build directories. + private def injectIntoMetaMetaBuild(projectName: String, sbtVersion: String, extractionVersion: String) = { + sbt.IO.withTemporaryDirectory { projectDir => - val config = - """|build: { - | check-missing: [false, false, false] - | cross-version: standard - | space: test - | sbt-version: "1.13.0" - | extraction-version: "2.12.21" - | projects: [ - | { - | name: InjectionTest - | check-missing: false - | uri: "nil://" - | extra: { - | settings: [ - | [], - | [], - | [ - | "name := \"Hello-""".stripMargin + System.currentTimeMillis + """\"" - | ] - | ] - | run-tests: false - | } - | } - | ] - |} - |""".stripMargin + // Build the config text from the same literal template the test always used (with the + // sbt/extraction versions and project name as placeholders, substituted below), to avoid + // disturbing the delicate quoting trick used to embed a literal, timestamp-suffixed + // "name := ..." line. + val configTemplate = + """|build: { + | check-missing: [false, false, false] + | cross-version: standard + | space: test + | sbt-version: "SBT_VERSION_PLACEHOLDER" + | extraction-version: "EXTRACTION_VERSION_PLACEHOLDER" + | projects: [ + | { + | name: PROJECT_NAME_PLACEHOLDER + | check-missing: false + | uri: "nil://" + | extra: { + | settings: [ + | [], + | [], + | [ + | "name := \"Hello-""".stripMargin + System.currentTimeMillis + """\"" + | ] + | ] + | run-tests: false + | } + | } + | ] + |} + |""".stripMargin + val config = configTemplate + .replace("SBT_VERSION_PLACEHOLDER", sbtVersion) + .replace("EXTRACTION_VERSION_PLACEHOLDER", extractionVersion) + .replace("PROJECT_NAME_PLACEHOLDER", projectName) - val endConfig = ConfigFactory.parseString(config) - val conf = readValueT[DBuildConfiguration](endConfig) + val endConfig = ConfigFactory.parseString(config) + val conf = readValueT[DBuildConfiguration](endConfig) - val repoStrings = Seq( - "local", // this will point to /.dbuild/topIvy/ivy2/local - "maven-central", - // this is where "publishLocal" pushed its modules (at least if the Ivy cache is in the default location) - "localIvy: file:"+System.getProperty("user.home")+"/.ivy2/local, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", - "sonatype-snapshots: https://oss.sonatype.org/content/repositories/snapshots", - "sonatype-releases: https://oss.sonatype.org/content/repositories/releases", - "typesafe-releases: https://repo.typesafe.com/typesafe/releases", - "typesafe-ivy-releases: https://repo.typesafe.com/typesafe/ivy-releases, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", - "typesafe-ivy-snapshots: https://repo.typesafe.com/typesafe/ivy-snapshots, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", - "sbt-plugin-releases: https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", - "scala-fresh-2.10.x: https://repo.typesafe.com/typesafe/scala-fresh-2.10.x/" - ) - val repoRecords = repoStrings map { - _.split(":", 2) match { - case Array(x) => (x, None) - case Array(x, y) => (x, Some(y)) - case z => sys.error("Internal error, unexpected split result: " + z) - }} + val repoStrings = Seq( + "local", // this will point to /.dbuild/topIvy/ivy2/local + "maven-central", + // this is where "publishLocal" pushed its modules (at least if the Ivy cache is in the default location) + "localIvy: file:"+System.getProperty("user.home")+"/.ivy2/local, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", + "sonatype-snapshots: https://oss.sonatype.org/content/repositories/snapshots", + "sonatype-releases: https://oss.sonatype.org/content/repositories/releases", + "typesafe-releases: https://repo.typesafe.com/typesafe/releases", + "typesafe-ivy-releases: https://repo.typesafe.com/typesafe/ivy-releases, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", + "typesafe-ivy-snapshots: https://repo.typesafe.com/typesafe/ivy-snapshots, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", + "sbt-plugin-releases: https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", + "scala-fresh-2.10.x: https://repo.typesafe.com/typesafe/scala-fresh-2.10.x/" + ) + val repoRecords = repoStrings map { + _.split(":", 2) match { + case Array(x) => (x, None) + case Array(x, y) => (x, Some(y)) + case z => sys.error("Internal error, unexpected split result: " + z) + }} - val repos = Repositories.parseRepositories(repoRecords) + val repos = Repositories.parseRepositories(repoRecords) - val main = new LocalBuildMain(repos, BuildRunOptions(CleanupOptions(), Timeouts(), true, true)) - val outcome = try { - main.build(conf, "InjectionTest", None) - } finally main.dispose() + val main = new LocalBuildMain(repos, BuildRunOptions(CleanupOptions(), Timeouts(), true, true)) + val outcome = try { + main.build(conf, projectName, None) + } finally main.dispose() - // TODO - We expect no output, but we do expet a successfull run. - outcome.status.toString must equalTo("SUCCESS (project rebuilt ok)") - } + // TODO - We expect no output, but we do expet a successfull run. + outcome.status().toString must equalTo("SUCCESS (project rebuilt ok)") } } } diff --git a/build/src/main/scala/com/typesafe/dbuild/build/Checkout.scala b/build/src/main/scala/com/typesafe/dbuild/build/Checkout.scala index 0e98a6d35..dcaa14e5c 100644 --- a/build/src/main/scala/com/typesafe/dbuild/build/Checkout.scala +++ b/build/src/main/scala/com/typesafe/dbuild/build/Checkout.scala @@ -1,22 +1,22 @@ package com.typesafe.dbuild.build -import java.io.File -import com.typesafe.dbuild.repo.core.{ Repository, LocalRepoHelper } -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.project.resolve.AggregateProjectResolver import com.typesafe.dbuild.logging.ConsoleLogger +import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.project.build.LocalBuildRunner -import com.typesafe.dbuild.utils.TrackedProcessBuilder -import sbt.io.Path._ -import sbt.io.syntax._ -import sys.process._ +import com.typesafe.dbuild.project.resolve.AggregateProjectResolver +import com.typesafe.dbuild.repo.core.{ Repository, LocalRepoHelper } +import com.typesafe.dbuild.support.sbt.Repositories import com.typesafe.dbuild.support.sbt.SbtBuildSystem -import scala.collection.immutable.SortedMap +import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames.* import com.typesafe.dbuild.support.sbt.{ SbtRunner, SbtBuildConfig } -import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames._ -import com.typesafe.dbuild.support.sbt.Repositories -import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.utils.TrackedProcessBuilder +import java.io.File import java.io.PrintWriter +import sbt.io.Path.* +import sbt.io.syntax.* +import scala.collection.immutable.SortedMap +import sys.process.* /** * Implementation of the "dbuild checkout" command. It will resolve and reconfigure an sbt project @@ -115,12 +115,12 @@ object Checkout { writer.println("echo 'Preparing sbt. This may take a while...'") writer.println(cmd.map { _.replaceAll("'", "'\"'\"'") }.mkString("'", "' '", "'")) writer.close() - Seq("chmod", "u+x", commandFile.getCanonicalPath) !; + Seq("chmod", "u+x", commandFile.getCanonicalPath).! println() println("Ready! You can start the debugging environment by running: " + commandFile.getCanonicalPath) println() println("You can also rebuild the project just like dbuild would do, by issuing \"dbuild-build\" at the sbt prompt.") - }), targetCommands = Seq("shell")) + }), targetCommands = Seq("shell"))() case _ => sys.error("This build UUID was found, but its data seems corrupted. Please report.") } diff --git a/build/src/main/scala/com/typesafe/dbuild/build/Comparison.scala b/build/src/main/scala/com/typesafe/dbuild/build/Comparison.scala index e032ea502..a2d0a088a 100644 --- a/build/src/main/scala/com/typesafe/dbuild/build/Comparison.scala +++ b/build/src/main/scala/com/typesafe/dbuild/build/Comparison.scala @@ -1,19 +1,19 @@ package com.typesafe.dbuild.build -import sbt.io.{ IO, PathFinder } -import sbt.io.syntax._ -import com.typesafe.dbuild.model._ import com.typesafe.dbuild.logging.Logger -import java.io.File +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.SeqStringH.* import com.typesafe.dbuild.repo.core.{ LocalRepoHelper, Repository } -import org.apache.commons.io.{ FileUtils, IOUtils } -import collection.JavaConversions._ -import java.util.jar.JarInputStream -import java.util.jar.JarFile -import java.util.jar.JarEntry +import java.io.File import java.io.FileInputStream +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarInputStream +import org.apache.commons.io.{ FileUtils, IOUtils } import org.apache.oro.text.regex -import com.typesafe.dbuild.model.SeqStringH._ +import sbt.io.syntax.* +import sbt.io.{ IO, PathFinder } +import scala.jdk.CollectionConverters.* class Comparison(options: GeneralOptions, log: Logger) extends OptionTask(log) { def id = "Comparison" @@ -32,7 +32,7 @@ class Comparison(options: GeneralOptions, log: Logger) extends OptionTask(log) { // we do not run comparison if we reached time out, or if we never arrived past extraction (or both) if (outcome.isInstanceOf[TimedOut]) dontRun() else optRepBuild match { - case None => dontRun + case None => dontRun() case Some(repBuild) => checkComparison(options, repBuild, outcome, log) } } @@ -64,8 +64,8 @@ class Comparison(options: GeneralOptions, log: Logger) extends OptionTask(log) { if (badB.isEmpty) { val logLimit = 10 // excellent! We just need to compare the jars in dirA and dirB - val jarsA = PathFinder(dirA).allPaths.get.filter(_.getName.endsWith(".jar")) - val jarsB = PathFinder(dirB).allPaths.get.filter(_.getName.endsWith(".jar")) + val jarsA = PathFinder(dirA).allPaths.get().filter(_.getName.endsWith(".jar")) + val jarsB = PathFinder(dirB).allPaths.get().filter(_.getName.endsWith(".jar")) def checkPaths(x: Seq[String], y: Seq[String], xName: String, yName: String) = { val xNotY = x.diff(y) val ok = xNotY.isEmpty @@ -95,10 +95,10 @@ class Comparison(options: GeneralOptions, log: Logger) extends OptionTask(log) { // But globs are a bit easier to use in this context. val skipGlobPatterns = check.skip.map(new org.apache.oro.text.GlobCompiler().compile(_)) val matcher = new regex.Perl5Matcher() - pathsA.foreach { p: String => + pathsA.foreach { (p: String) => val fa = new File(dirA, p) val fb = new File(dirB, p) - JarFiles.compareJars(fa, fb, p, { name: String => skipGlobPatterns.exists(matcher.matches(name, _)) }, log, logLimit) + JarFiles.compareJars(fa, fb, p, { (name: String) => skipGlobPatterns.exists(matcher.matches(name, _)) }, log, logLimit) } log.info("Comparison OK.") } @@ -119,14 +119,14 @@ object JarFiles { // jarName is some name jar info that will be printed as diagnostic in case of error // log is a Logger def compareJars(fa: File, fb: File, jarName: String, log: Logger): Unit = - compareJars(fa, fb, jarName, { _: String => true }, log, 10) + compareJars(fa, fb, jarName, { (_: String) => true }, log, 10) // additional parameters: // within a jar archive, only compare files when their names are not skipped (according to "skip" below) // logLimit is a limits on the number of differing elements that will be printed in case of errors def compareJars(fa: File, fb: File, jarName: String, skip: String => Boolean, log: Logger, logLimit: Int): Unit = { def getEntries(jf: JarFile) = { - jf.entries.map { je => + jf.entries.asScala.map { je => val name = je.getName() if (je.isDirectory() || skip(name)) None diff --git a/build/src/main/scala/com/typesafe/dbuild/build/LocalBuildMain.scala b/build/src/main/scala/com/typesafe/dbuild/build/LocalBuildMain.scala index 9835f364b..fb1a348d5 100644 --- a/build/src/main/scala/com/typesafe/dbuild/build/LocalBuildMain.scala +++ b/build/src/main/scala/com/typesafe/dbuild/build/LocalBuildMain.scala @@ -1,29 +1,29 @@ package com.typesafe.dbuild.build -import java.io.File -import akka.actor.{ ActorSystem, Props } -import scala.concurrent.Await -import akka.util.Timeout -import akka.pattern.AskTimeoutException -import scala.concurrent.duration._ -import scala.concurrent.Future -import scala.util.{Success,Failure} -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.model.Utils.{ readValue, writeValue } -import com.typesafe.dbuild.repo.core._ +import com.typesafe.config.ConfigFactory +import com.typesafe.dbuild.logging +import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.logging.Logger.prepareLogMsg +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.model.ClassLoaderMadness +import com.typesafe.dbuild.model.Utils.{ readValue, writeValue } import com.typesafe.dbuild.project.dependencies.Extractor -import com.typesafe.dbuild.support.BuildSystemCore -import com.typesafe.dbuild.logging.Logger.prepareLogMsg -import akka.pattern.ask +import com.typesafe.dbuild.repo.core.* import com.typesafe.dbuild.repo.core.GlobalDirs.checkForObsoleteDirs import com.typesafe.dbuild.support -import com.typesafe.dbuild.logging -import akka.actor.{DeadLetter, Actor} -import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.support.BuildSystemCore import com.typesafe.dbuild.utils.TrackedProcessBuilder +import java.io.File +import org.apache.pekko.actor.{ ActorSystem, Props } +import org.apache.pekko.actor.{DeadLetter, Actor} +import org.apache.pekko.pattern.AskTimeoutException +import org.apache.pekko.pattern.ask +import org.apache.pekko.util.Timeout +import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global -import com.typesafe.config.ConfigFactory +import scala.concurrent.Future +import scala.concurrent.duration.* +import scala.util.{Success,Failure} class DeadLetterMonitorActor(logger: Logger) extends Actor { @@ -61,14 +61,14 @@ class LocalBuildMain(repos: List[xsbti.Repository], options: BuildRunOptions) { // Gymnastics for classloader madness val system = ClassLoaderMadness.withContextLoader(getClass.getClassLoader) { - val conf = ConfigFactory.parseString("akka.log-dead-letters-during-shutdown: off\n" + - "akka.log-dead-letters: off" ) + val conf = ConfigFactory.parseString("pekko.log-dead-letters-during-shutdown: off\n" + + "pekko.log-dead-letters: off" ) ActorSystem.create("dbuild", conf) } val logMgr = { val mgr = system.actorOf(Props(new logging.ChainedLoggerSupervisorActor), "ChainedLoggerSupervisorActor") - val logDirManagerActor = (mgr ? Props(new logging.LogDirManagerActor(new File(targetDir, "logs"))))(1 minute) - val systemOutLoggerActor = (mgr ? Props(new logging.SystemOutLoggerActor(options.debug)))(1 minute) + val logDirManagerActor = (mgr ? Props(new logging.LogDirManagerActor(new File(targetDir, "logs"))))(Timeout(1.minute)) + val systemOutLoggerActor = (mgr ? Props(new logging.SystemOutLoggerActor(options.debug)))(Timeout(1.minute)) mgr } val repository = Repository.default @@ -96,10 +96,10 @@ class LocalBuildMain(repos: List[xsbti.Repository], options: BuildRunOptions) { TrackedProcessBuilder.abortAll() implicit val timeout: Timeout = 3.minutes Await.result((logMgr ? "exit").mapTo[String], Duration.Inf) - system.shutdown() // pro forma, as all loggers should already be stopped at this point + system.terminate() // pro forma, as all loggers should already be stopped at this point try { println("Shutting down, please wait...") - system.awaitTermination(4.minute) + Await.ready(system.whenTerminated, 4.minute) } catch { case e:Exception => println("Warning: system did not shut down within the allotted time") } diff --git a/build/src/main/scala/com/typesafe/dbuild/build/LocalBuilderActor.scala b/build/src/main/scala/com/typesafe/dbuild/build/LocalBuilderActor.scala index 5472cfa73..24898ed74 100644 --- a/build/src/main/scala/com/typesafe/dbuild/build/LocalBuilderActor.scala +++ b/build/src/main/scala/com/typesafe/dbuild/build/LocalBuilderActor.scala @@ -1,16 +1,16 @@ package com.typesafe.dbuild.build +import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.project.BuildSystem -import com.typesafe.dbuild.support.BuildSystemCore -import com.typesafe.dbuild.project.resolve.ProjectResolver -import com.typesafe.dbuild.project.dependencies.TimedExtractorActor import com.typesafe.dbuild.project.build.TimedBuildRunnerActor -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.logging.Logger -import akka.actor.{Actor,Props,ActorRef,ActorContext} -import java.io.File -import com.typesafe.dbuild.repo.core.Repository import com.typesafe.dbuild.project.dependencies.Extractor +import com.typesafe.dbuild.project.dependencies.TimedExtractorActor +import com.typesafe.dbuild.project.resolve.ProjectResolver +import com.typesafe.dbuild.repo.core.Repository +import com.typesafe.dbuild.support.BuildSystemCore +import java.io.File +import org.apache.pekko.actor.{Actor,Props,ActorRef,ActorContext} case class RunLocalBuild(config: DBuildConfiguration, configName: String, buildTarget: Option[String]) /** diff --git a/build/src/main/scala/com/typesafe/dbuild/build/OptionTask.scala b/build/src/main/scala/com/typesafe/dbuild/build/OptionTask.scala index 442de2221..bd95666ff 100644 --- a/build/src/main/scala/com/typesafe/dbuild/build/OptionTask.scala +++ b/build/src/main/scala/com/typesafe/dbuild/build/OptionTask.scala @@ -1,13 +1,12 @@ package com.typesafe.dbuild.build -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.logging.Logger import com.typesafe.dbuild.deploy.Creds.loadCreds +import com.typesafe.dbuild.logging.Logger import Logger.prepareLogMsg -import com.typesafe.dbuild.model._ -import java.io.File +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.SeqStringH.* import com.typesafe.dbuild.repo.core.{ LocalRepoHelper, Repository } -import com.typesafe.dbuild.model.SeqStringH._ +import java.io.File /** * Defines a task that will run before or after the build, defined somewhere diff --git a/build/src/main/scala/com/typesafe/dbuild/build/SbtBuildMain.scala b/build/src/main/scala/com/typesafe/dbuild/build/SbtBuildMain.scala index 09157a6ca..726f011a2 100644 --- a/build/src/main/scala/com/typesafe/dbuild/build/SbtBuildMain.scala +++ b/build/src/main/scala/com/typesafe/dbuild/build/SbtBuildMain.scala @@ -1,33 +1,32 @@ package com.typesafe.dbuild.build -import java.io.File -import com.typesafe.dbuild.model.DBuildConfiguration -import com.typesafe.dbuild.model.Utils.{ writeValue, readValue, readProperties } +import collection.JavaConverters.* +import collection.immutable.SortedMap +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigRenderOptions +import com.typesafe.config.ConfigValueFactory +import com.typesafe.config.{ ConfigSyntax, ConfigFactory, ConfigParseOptions } +import com.typesafe.dbuild.adapter.Defaults import com.typesafe.dbuild.model.ClassLoaderMadness -import com.typesafe.dbuild.model.{ BuildOutcome, BuildBad } -import com.typesafe.dbuild.model.TemplateFormatter import com.typesafe.dbuild.model.CleanupOptions +import com.typesafe.dbuild.model.DBuildConfiguration +import com.typesafe.dbuild.model.SeqDBCH.* +import com.typesafe.dbuild.model.SeqStringH.* +import com.typesafe.dbuild.model.TemplateFormatter import com.typesafe.dbuild.model.Timeouts -import java.util.Properties -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigValueFactory import com.typesafe.dbuild.model.Utils.readValueT +import com.typesafe.dbuild.model.Utils.{ writeValue, readValue, readProperties } +import com.typesafe.dbuild.model.{ BuildOutcome, BuildBad } +import com.typesafe.dbuild.support.sbt.Repositories import com.typesafe.dbuild.utils.Time.timed import com.typesafe.dbuild.utils.TrackedProcessBuilder -import collection.immutable.SortedMap -import com.typesafe.dbuild.adapter.Defaults -import com.typesafe.dbuild.support.sbt.Repositories -import com.typesafe.config.{ ConfigSyntax, ConfigFactory, ConfigParseOptions } -import org.rogach.scallop._ -import org.rogach.scallop.exceptions.ScallopException +import java.io.File import java.text.SimpleDateFormat import java.util.Locale +import java.util.Properties import java.util.TimeZone -import collection.JavaConverters._ -import scala.collection.JavaConversions._ -import com.typesafe.config.ConfigRenderOptions -import com.typesafe.dbuild.model.SeqDBCH._ -import com.typesafe.dbuild.model.SeqStringH._ +import org.rogach.scallop.* +import org.rogach.scallop.exceptions.ScallopException /** * These options are created by SbtBuildMain, and are propagated to most stages of building, as @@ -47,8 +46,8 @@ class SbtBuildMain extends xsbti.AppMain { } catch { case e: java.io.IOException => false } - if (result) println(url + " has xsbti.Logger class") - else println(url + " - nada") + if (result) println(url.toString + " has xsbti.Logger class") + else println(url.toString + " - nada") } def printClassLoaders(cl: ClassLoader): Unit = cl match { @@ -82,6 +81,18 @@ class SbtBuildMain extends xsbti.AppMain { // first thing upon start, in sbt/launch/src/main/scala/xsbt/boot/Boot.scala) // val args = configuration.arguments + // Named (rather than anonymous) so that its extra members (uuid/project/path) remain + // visible on "checkout"/"conf.checkout" -- Scala 3 does not infer a structural/refined + // type for a val initialized from "new Subcommand(...) { ... }", unlike Scala 2. + class CheckoutSubcommand extends Subcommand("checkout") { + banner("""Use "dbuild checkout" to check out one project from a previously compiled + |build, preparing sbt for a debugging session. + |Options: + |""".stripMargin) + val uuid = trailArg[String](descr = "UUID of the build") + val project = trailArg[String](descr = "name of the project") + val path = trailArg[String](descr = "path into which the source will be checked out") + } object conf extends ScallopConf(args.toList) { printedName = "dbuild" version("Typesafe dbuild " + Defaults.version + " (" + Defaults.hash.take(12) + ")") @@ -98,15 +109,7 @@ class SbtBuildMain extends xsbti.AppMain { val noResolvers = opt[Boolean](short = 'r', descr = "Disable the parsing of the \"options.resolvers\" section from the dbuild configuration file: only use the resolvers defined in dbuild.properties") val noNotify = opt[Boolean](short = 'n', descr = "Disable the notifications defined in the configuration file, and only print a report on the console") val local = opt[Boolean](short = 'l', descr = "Equivalent to: --no-resolvers --no-notify") - val checkout = new Subcommand("checkout") { - banner("""Use "dbuild checkout" to check out one project from a previously compiled - |build, preparing sbt for a debugging session. - |Options: - |""".stripMargin) - val uuid = trailArg[String](descr = "UUID of the build") - val project = trailArg[String](descr = "name of the project") - val path = trailArg[String](descr = "path into which the source will be checked out") - } + val checkout = new CheckoutSubcommand conflicts(noNotify, List(checkout.uuid, checkout.project, checkout.path)) conflicts(local, List(checkout.uuid, checkout.project, checkout.path)) // requireOne(checkout.uuid,configFile) // use manual checking (below) to get a better error message @@ -171,7 +174,7 @@ class SbtBuildMain extends xsbti.AppMain { // Parse them even if useLocalResolvers==true, in order to catch definition errors val explicitResolvers = SortedMap[String, (String, Option[String])]() ++ (if (resolvedConfig.hasPath("options.resolvers")) { - import collection.JavaConverters._ + import collection.JavaConverters.* val map = resolvedConfig.getObject("options.resolvers").unwrapped().asScala map.map { case (k, v) => (k, @@ -189,11 +192,11 @@ class SbtBuildMain extends xsbti.AppMain { if (debug) { println("Environment variables:") val environmentVars = System.getenv - for ((k, v) <- environmentVars) println(k + ": " + v) + for ((k, v) <- environmentVars.asScala) println(k + ": " + v) println("\nContent of the \"vars\" section:") try { // in case resolve() fails val resolved = endConfig.withOnlyPath("vars").resolve - val entries = resolved.entrySet.toSeq.sortBy(_.getKey) + val entries = resolved.entrySet.asScala.toSeq.sortBy(_.getKey) val opt = ConfigRenderOptions.concise.setFormatted(true) entries foreach { entry => println(entry.getKey + ": " + entry.getValue.render(opt)) } } catch { case e: Exception => println("Unexpected while printing: " + e.getMessage()) } @@ -259,7 +262,7 @@ class SbtBuildMain extends xsbti.AppMain { val (outcome, time) = try { timed { main.build(finalConfig, configFile.getName, buildTarget) } } finally main.dispose() - println("Result: " + outcome.status) + println("Result: " + outcome.status()) println("Build " + (if (outcome.isInstanceOf[BuildBad]) "failed" else "succeeded") + " after: " + time) println("All done.") if (outcome.isInstanceOf[BuildBad]) Exit(1) else Exit(0) diff --git a/build/src/main/scala/com/typesafe/dbuild/build/SimpleBuildActor.scala b/build/src/main/scala/com/typesafe/dbuild/build/SimpleBuildActor.scala index 0bdd631da..9c1a84df8 100644 --- a/build/src/main/scala/com/typesafe/dbuild/build/SimpleBuildActor.scala +++ b/build/src/main/scala/com/typesafe/dbuild/build/SimpleBuildActor.scala @@ -1,27 +1,26 @@ package com.typesafe.dbuild.build -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.project.build._ -import com.typesafe.dbuild.repo.core.{ Repository, LocalRepoHelper } -import com.typesafe.dbuild.project.dependencies.ExtractBuildDependencies -import com.typesafe.dbuild.support.BuildSystemCore -import com.typesafe.dbuild.project.{ BuildSystem, BuildData } -import com.typesafe.dbuild.logging.Logger -import com.typesafe.dbuild.hashing import com.typesafe.dbuild.graph +import com.typesafe.dbuild.hashing +import com.typesafe.dbuild.logging.Logger import Logger.prepareLogMsg -import akka.actor.{ Actor, ActorRef, Props } -import akka.pattern.{ ask, pipe, after } -import akka.pattern.AskTimeoutException -import scala.concurrent.duration._ -import scala.concurrent.{ Future, Promise, ExecutionContext } -import ExecutionContext.Implicits.global -import akka.util.Timeout -import java.io.File +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.SeqDBCH.* +import com.typesafe.dbuild.project.build.* +import com.typesafe.dbuild.project.dependencies.ExtractBuildDependencies +import com.typesafe.dbuild.project.{ BuildSystem, BuildData } import com.typesafe.dbuild.repo.core.GlobalDirs +import com.typesafe.dbuild.repo.core.{ Repository, LocalRepoHelper } +import com.typesafe.dbuild.support.BuildSystemCore +import java.io.File import org.apache.maven.execution.BuildFailure -import Logger.prepareLogMsg -import com.typesafe.dbuild.model.SeqDBCH._ +import org.apache.pekko.actor.{ Actor, ActorRef, Props } +import org.apache.pekko.pattern.AskTimeoutException +import org.apache.pekko.pattern.{ ask, pipe, after } +import org.apache.pekko.util.Timeout +import scala.concurrent.duration.* +import scala.concurrent.{ Future, Promise, ExecutionContext } +import ExecutionContext.Implicits.global case class RunDBuild(conf: DBuildConfiguration, confName: String, buildTarget: Option[String], logger: Logger, options: BuildRunOptions) @@ -33,13 +32,13 @@ class SimpleBuildActor(extractor: ActorRef, builder: ActorRef, repository: Repos final def forwardingErrorsToFutures[A](sender: ActorRef)(f: => A): A = try f catch { case e: Exception => - sender ! akka.actor.Status.Failure(e) + sender ! org.apache.pekko.actor.Status.Failure(e) throw e } def receive = { - case RunDBuild(inputConf, confName, buildTarget, log, options) => forwardingErrorsToFutures(sender) { - val listener = sender + case RunDBuild(inputConf, confName, buildTarget, log, options) => forwardingErrorsToFutures(sender()) { + val listener = sender() val extractionPhaseDuration = options.timeouts.extractionPhaseTimeout val buildPhaseDuration = options.timeouts.buildPhaseTimeout @@ -342,7 +341,7 @@ class SimpleBuildActor(extractor: ActorRef, builder: ActorRef, repository: Repos ExtractionFailed(projConfig.name, Seq(), "Cause: " + prepareLogMsg(log, e)) } } - futureOutcomes map { s: Seq[ExtractionOutcome] => + futureOutcomes map { (s: Seq[ExtractionOutcome]) => if (s exists { _.isInstanceOf[ExtractionFailed] }) { if (s exists { _.isInstanceOf[TimedOut] }) new ExtractionFailed(".", s, "Timeout: the extraction phase took longer than " + extractionPhaseDuration) with TimedOut diff --git a/core/src/main/scala/com/typesafe/dbuild/project/BuildSystem.scala b/core/src/main/scala/com/typesafe/dbuild/project/BuildSystem.scala index 8b0b0b0db..b0e937336 100644 --- a/core/src/main/scala/com/typesafe/dbuild/project/BuildSystem.scala +++ b/core/src/main/scala/com/typesafe/dbuild/project/BuildSystem.scala @@ -1,10 +1,10 @@ package com.typesafe.dbuild.project -import com.typesafe.dbuild.model._ import com.typesafe.dbuild.logging.Logger -import java.io.File -import com.typesafe.dbuild.adapter.Adapter.Path._ +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.utils.TrackedProcessBuilder +import java.io.File +import sbt.io.Path.* case class BuildData(log: Logger, debug: Boolean) diff --git a/core/src/main/scala/com/typesafe/dbuild/utils/InterruptibleProcess.scala b/core/src/main/scala/com/typesafe/dbuild/utils/InterruptibleProcess.scala index 8d2510869..41442ac8b 100644 --- a/core/src/main/scala/com/typesafe/dbuild/utils/InterruptibleProcess.scala +++ b/core/src/main/scala/com/typesafe/dbuild/utils/InterruptibleProcess.scala @@ -3,7 +3,7 @@ // timeout occurs. One wrapper can keep track of only one Process at a time, // so there will be usually one wrapper per build/extraction worker. package com.typesafe.dbuild.utils -import sys.process._ +import sys.process.* class TrackedProcessBuilder { var interrupted = false @@ -65,7 +65,7 @@ class TrackedProcessBuilder { } object TrackedProcessBuilder { - val trackers = new scala.collection.mutable.MutableList[TrackedProcessBuilder] + val trackers = new scala.collection.mutable.ListBuffer[TrackedProcessBuilder] def record(t:TrackedProcessBuilder) = synchronized { trackers += t diff --git a/deploy/src/main/scala/com/typesafe/dbuild/deploy/Deploy.scala b/deploy/src/main/scala/com/typesafe/dbuild/deploy/Deploy.scala index 458bc2983..bb59d2eb7 100644 --- a/deploy/src/main/scala/com/typesafe/dbuild/deploy/Deploy.scala +++ b/deploy/src/main/scala/com/typesafe/dbuild/deploy/Deploy.scala @@ -1,26 +1,30 @@ package com.typesafe.dbuild.deploy -import sbt._ -import java.io.File -import java.net.URI -import com.amazonaws.services.s3.AmazonS3Client -import com.amazonaws.services.s3.model.PutObjectRequest -import com.amazonaws.auth.BasicAWSCredentials +import Creds.loadCreds +import _root_.io.circe.Decoder +import _root_.io.circe.generic.semiauto.deriveDecoder +import _root_.io.circe.parser.parse import com.amazonaws.ClientConfiguration import com.amazonaws.Protocol -import Creds.loadCreds -import com.jcraft.jsch.{ IO => sshIO, Logger => _, _ } -import java.util.Date +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.services.s3.AmazonS3Client +import com.amazonaws.services.s3.model.PutObjectRequest import com.jcraft.jsch.ChannelSftp -import com.typesafe.dbuild.adapter.Adapter -import Adapter.{IO,Logger,allPaths} -import com.lambdaworks.jacks.JacksMapper -import Adapter.Path._ -import Adapter.syntaxio._ -import dispatch.{url => dispUrl, Http} +import com.jcraft.jsch.{ IO => sshIO, Logger => _, * } +import com.typesafe.dbuild.adapter.Adapter.allPaths +import gigahorse.HttpClient +import gigahorse.support.apachehttp.Gigahorse +import java.io.File +import java.net.URI +import java.util.Date +import sbt.* +import sbt.io.IO +import sbt.io.Path.* +import sbt.io.syntax.* +import sbt.util.Logger +import scala.concurrent.* import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent._ -import scala.concurrent.duration._ +import scala.concurrent.duration.* /** * A generic (S3, http, https, etc) deploy location. @@ -56,7 +60,7 @@ abstract class DeployTarget extends DeployInfo { * given directory, to some remote (or local) target. */ abstract class Deploy[T](options: DeployInfo) { - def deploy[T](dir: File) + def deploy[T](dir: File): Unit } /** * Use Deploy.deploy(target,dir,log) to deploy a set of artifacts, contained in the directory "dir", @@ -77,19 +81,18 @@ object Deploy { } def isNotChecksum(path: String): Boolean = !(path.endsWith(".sha1") || path.endsWith(".md5")) - // Use jacks and Jackson to read a /possible/ response from Artifactory + // Read a /possible/ response from Artifactory. // This readSomePath() is essentially a duplicate of the one defined in package Utils, // replicated here in order to allow the use of this "deploy" package without importing // additional dbuild packages, or unnecessary dependencies. - private val mapper = JacksMapper - private[deploy] def readSomePath[T](s: String)(implicit m: Manifest[T]) = { + private[deploy] def readSomePath[T](s: String)(implicit d: Decoder[T]) = { val current = Thread.currentThread.getContextClassLoader try { Thread.currentThread setContextClassLoader getClass.getClassLoader try { - Some(mapper.readValue[T](s)) + parse(s).flatMap(_.as[T]).toOption } catch { - case e: com.fasterxml.jackson.databind.JsonMappingException => None + case e: _root_.io.circe.Error => None } } finally Thread.currentThread setContextClassLoader current } @@ -103,8 +106,8 @@ object Deploy { abstract class IterativeDeploy[T](options: DeployInfo) extends Deploy[T](options) { import Deploy.isNotChecksum protected def init(): T - protected def message(relative: String) - protected def deployItem(handler: T, relative: String, file: File, targetURI: URI) + protected def message(relative: String): Unit + protected def deployItem(handler: T, relative: String, file: File, targetURI: URI): Unit protected def close(handler: T) = () val credentials = options.creds getOrElse sys.error("No credentials supplied for uri " + options.uri) @@ -118,7 +121,7 @@ abstract class IterativeDeploy[T](options: DeployInfo) extends Deploy[T](options * order, in order to comply with the peculiar requirements of * Maven/Ivy repositories (Artifactory, in particular). */ - def deploy[T](dir: File) { + def deploy[T](dir: File): Unit = { val targetBaseURI = new java.net.URI(options.uri) val handler = init() try { @@ -133,7 +136,7 @@ abstract class IterativeDeploy[T](options: DeployInfo) extends Deploy[T](options // - finally, the checksums are uploaded after the main artifacts // - val allFiles = allPaths(dir).get.filter(f => !f.isDirectory) + val allFiles = allPaths(dir).get().filter(f => !f.isDirectory) val poms = allFiles.filter(f => f.getName.endsWith("-SNAPSHOT.pom")) // // we fold over the poms, reducing the set of files until we are left with just @@ -144,7 +147,7 @@ abstract class IterativeDeploy[T](options: DeployInfo) extends Deploy[T](options val thisDir = pomFile.getParentFile() if (thisDir == null) sys.error("Unexpected: file has not parent in deploy") // select the files in this directory (but not in subdirectories) - val theseFiles = allPaths(thisDir).get.filter(f => !f.isDirectory && f.getParentFile() == thisDir) + val theseFiles = allPaths(thisDir).get().filter(f => !f.isDirectory && f.getParentFile() == thisDir) // if there is a matching jar, upload the jar first val jarFile = new java.io.File(pomFile.getPath().replaceAll("\\.[^\\.]*$", ".jar")) val thisSeq = if (theseFiles contains jarFile) { @@ -185,7 +188,7 @@ class DeploySSH(log: Logger, options: DeployInfo) extends IterativeDeploy[Channe import log.{ debug => ld, info => li, warn => lw, error => le } JSch.setLogger(new com.jcraft.jsch.Logger { def isEnabled(level: Int) = true - import com.jcraft.jsch.Logger._ + import com.jcraft.jsch.Logger.* def log(level: Int, message: String) = level match { // levels are arranged so that ssh info is debug for dbuild case DEBUG => ld(message) @@ -198,8 +201,8 @@ class DeploySSH(log: Logger, options: DeployInfo) extends IterativeDeploy[Channe // try to locate a private key; if it exists, add // the identity (for passwordless authentication) // Only the default location is supported, and no passphrase - import Adapter.syntaxio._ - import Adapter.Path._ + import sbt.io.Path.* + import sbt.io.syntax.* val privateKeyLocation = new File(System.getProperty("user.home")) / ".ssh" / "id_rsa" try { val is = new java.io.FileInputStream(privateKeyLocation) @@ -276,21 +279,23 @@ class DeployNull(log: Logger, options: DeployInfo) extends protected def deployItem(nothing: Unit, relative: String, file: File, uri: URI) = {} } -class DeployHTTP(log: Logger, options: DeployInfo, timeOut: Duration = 20 minutes) - extends IterativeDeploy[Unit](options) { +class DeployHTTP(log: Logger, options: DeployInfo, timeOut: Duration = 20.minutes) + extends IterativeDeploy[HttpClient](options) { import Deploy.isNotChecksum - protected def init() = () + protected def init() = Gigahorse.http(Gigahorse.config) + override protected def close(http: HttpClient) = http.close() protected def message(relative: String) = if (isNotChecksum(relative)) log.info("Deploying: " + relative) else log.info("Verifying checksum: " + relative) - protected def deployItem(handler: Unit, relative: String, file: File, uri: URI) = { - val sender = - dispUrl(uri.toString).PUT.as(credentials.user, credentials.pass).setBody(file).setContentType("application/octet-stream","UTF-8") - val response = Await.result(Http(sender OK { response => - Deploy.readSomePath[ArtifactoryResponse](response.getResponseBody) - }), timeOut) + protected def deployItem(http: HttpClient, relative: String, file: File, uri: URI) = { + val request = Gigahorse.url(uri.toString).put(file) + .withAuth(credentials.user, credentials.pass) + .withContentType("application/octet-stream") + .withRequestTimeout(timeOut) + val body = Await.result(http.run(request, Gigahorse.asString), timeOut) + val response = Deploy.readSomePath[ArtifactoryResponse](body) try { if (response != None && response.get.path != None && response.get.path.get != null) { val out = response.get.path.get.replaceFirst("^/", "") @@ -314,3 +319,6 @@ class DeployFiles(log: Logger, options: DeployInfo) extends Deploy[Unit](options // Response from Artifactory case class ArtifactoryResponse(path: Option[String]) +object ArtifactoryResponse { + implicit val artifactoryResponseDecoder: Decoder[ArtifactoryResponse] = deriveDecoder[ArtifactoryResponse] +} diff --git a/graph/src/main/scala/com/typesafe/dbuild/graph/Graphs.scala b/graph/src/main/scala/com/typesafe/dbuild/graph/Graphs.scala index 2ea1eea15..ebfc7efd0 100644 --- a/graph/src/main/scala/com/typesafe/dbuild/graph/Graphs.scala +++ b/graph/src/main/scala/com/typesafe/dbuild/graph/Graphs.scala @@ -31,7 +31,7 @@ abstract class Graph[N, E] extends GraphCore[N, E] { } def tarjanSubGraphs: Set[Graph[N, E]] = - tarjan map { setEdges: Set[Node[N]] => FilteredByNodesGraph(setEdges) } + tarjan map { (setEdges: Set[Node[N]]) => FilteredByNodesGraph(setEdges) } // Note this is used to detect cycles. Breaks // the graph into strongly connected subgraphs. @@ -81,7 +81,7 @@ abstract class Graph[N, E] extends GraphCore[N, E] { // I have no access to logging here, so I have to // create a long error message instead. // Nodes of the cycle, in the right order - val messages = (cycles map { comp: Set[Node[N]] => + val messages = (cycles map { (comp: Set[Node[N]]) => def connections(from: Node[N]) = { (edges(from) filter { n => comp.contains(n.to) } groupBy { _.to } flatMap { _._2 } map { _.toString }).mkString("", "\n", "\n") } diff --git a/graph/src/main/scala/com/typesafe/dbuild/graph/SimpleGraph.scala b/graph/src/main/scala/com/typesafe/dbuild/graph/SimpleGraph.scala index 9e031fac4..be0a74dfd 100644 --- a/graph/src/main/scala/com/typesafe/dbuild/graph/SimpleGraph.scala +++ b/graph/src/main/scala/com/typesafe/dbuild/graph/SimpleGraph.scala @@ -5,7 +5,7 @@ case class EmptyEdge[N](from: Node[N], to: Node[N]) extends Edge[N,Nothing] { def value: Nothing = sys.error("Empty edges have no data!") } case class SimpleEdge[N,E](from: Node[N], to: Node[N], value: E) extends Edge[N,E] { - override def toString = from + " -> " + to + "(" + value + ")" + override def toString = s"$from -> $to($value)" } case class SimpleGraph[N,E](nodes: Set[Node[N]], _edges: Map[Node[N], Seq[Edge[N,E]]]) extends Graph[N,E]{ def edges(n: Node[N]) = _edges get n getOrElse Seq.empty diff --git a/hashing/src/main/scala/com/typesafe/dbuild/hashing/package.scala b/hashing/src/main/scala/com/typesafe/dbuild/hashing/package.scala index cb6e137a7..45645ac6e 100644 --- a/hashing/src/main/scala/com/typesafe/dbuild/hashing/package.scala +++ b/hashing/src/main/scala/com/typesafe/dbuild/hashing/package.scala @@ -1,8 +1,8 @@ package com.typesafe.dbuild +import collection.JavaConverters.* import com.typesafe.config.ConfigValue -import collection.JavaConverters._ import java.nio.ByteBuffer -import scala.concurrent.duration._ +import scala.concurrent.duration.* package object hashing { @@ -27,13 +27,13 @@ package object hashing { // precisely negative durations in most Scala versions, see // Scala issues #9949 and #10320 case d: Duration => addBytes(d.toString) - case map: Map[String @unchecked,_] => + case map: Map[String @unchecked, ?] => val data = map.toSeq.sortBy(_._1) data foreach { case (k,v) => addBytes(k) addBytes(v) } - case s: Traversable[_] => + case s: Traversable[?] => // First add a traversable marker.. md update 1.toByte s foreach addBytes @@ -41,10 +41,10 @@ package object hashing { // Add a product marker md update 5.toByte s.productIterator foreach addBytes - case list: java.util.List[_] => + case list: java.util.List[?] => md update 1.toByte list.asScala foreach addBytes - case map: java.util.Map[String @unchecked,_] => + case map: java.util.Map[String @unchecked, ?] => val data = map.entrySet.iterator.asScala.toSeq.sortBy(_.getKey) data foreach { kv => addBytes(kv.getKey) diff --git a/http/src/main/scala/com/typesafe/dbuild/http/HttpTransfer.scala b/http/src/main/scala/com/typesafe/dbuild/http/HttpTransfer.scala index 4cfba191c..348c5a436 100644 --- a/http/src/main/scala/com/typesafe/dbuild/http/HttpTransfer.scala +++ b/http/src/main/scala/com/typesafe/dbuild/http/HttpTransfer.scala @@ -1,70 +1,29 @@ package com.typesafe.dbuild.http -import scala.language.postfixOps -import dispatch.{url => dispatchUrl,_} -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.concurrent.ExecutionContext.Implicits.global -import com.ning.http.client.{AsyncHttpClientConfig, AsyncHttpClient} +import gigahorse.HttpClient +import gigahorse.support.apachehttp.Gigahorse import java.io.File -import com.typesafe.dbuild.adapter.Adapter.IO +import sbt.io.IO +import scala.concurrent.Await +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration.* case class Credentials(user: String, pw: String) -class HttpTransfer(dbuildVersion:String) extends java.io.Closeable { - - // The "as.File" facility of dispatch leaks file descriptors, - // so we use this abstraction instead. - object AsFile { - import com.ning.http.client - def apply(file: java.io.File) = { - val tFile = new java.io.RandomAccessFile(file, "rw") - (new client.resumable.ResumableAsyncHandler with OkHandler[Nothing] { - override def onThrowable(t: Throwable): Unit = { - super.onThrowable(t) - try tFile.close() catch { case e: Throwable => } - } - }).setResumableListener( - new client.extra.ResumableRandomAccessFileListener( - tFile - ) - ) - } - } +class HttpTransfer(dbuildVersion: String) extends java.io.Closeable { - // I cannot use directly Http.configure(_ setFollowRedirect true), - // otherwise dispatch creates one instance for Http and another - // one for Http.configure(..). Both must be shut down, otherwise - // the application hangs and never exits. If the first one is - // shutdown once, it cannot be reused (it's a singleton). So, a mess. - // Therefore we manually create an instance here, which can be safely - // shut down at any time, and another one recreated using the same code. - val http = new Http( - new AsyncHttpClient( - new AsyncHttpClientConfig.Builder() - .setUserAgent("dbuild/%s" format dbuildVersion) - .setRequestTimeout(-1) - .setUseProxyProperties(true) - .setFollowRedirect(true) - .build() - ) - ) + private val http: HttpClient = Gigahorse.http(Gigahorse.config) - // If downloadTo() throws exception, or when the - // downloads are all complete, the caller MUST - // invoke .close() before exiting anyway, otherwise - // the application will hang. - def close():Unit = { - http.shutdown() - } + def close(): Unit = http.close() + private val userAgent = "dbuild/%s".format(dbuildVersion) private val encoding = java.nio.charset.StandardCharsets.UTF_8.toString() - def download(uri: String, file: File, timeOut: Duration = 10 minutes) = { + def download(uri: String, file: File, timeOut: Duration = 10.minutes): Unit = { // The uri is URL encoded in order to build a temporary // filename out of it val saneUri = java.net.URLEncoder.encode(uri, encoding) - val suffix=saneUri.substring(Math.max(0,saneUri.length-45)) + val suffix = saneUri.substring(Math.max(0, saneUri.length - 45)) val absFile = file.getAbsoluteFile() IO.withTemporaryFile("dbuild-download", suffix) { tmp => try { @@ -72,27 +31,34 @@ class HttpTransfer(dbuildVersion:String) extends java.io.Closeable { // an incorrect download w/ exception to leave behind // an old file that may be misinterpreted as the new one. if (absFile.exists) absFile.delete() - val r = http(dispatchUrl(uri) > AsFile(tmp)) - Await.result(r, timeOut) + val request = Gigahorse.url(uri) + .addHeaders("User-Agent" -> userAgent) + .withFollowRedirects(true) + .withRequestTimeout(timeOut) + Await.result(http.download(request, tmp), timeOut) // did all go ok? Move the file to the right place. // Note that IO.move() may choke if the dest file is not absolute IO.move(tmp, absFile) } catch { - case e:Exception => - throw new Exception("Error downloading " + absFile.getPath() + " from " + uri, e) + case e: Exception => + throw new Exception("Error downloading " + absFile.getPath() + " from " + uri, e) } } } - def upload(uri: String, file: File, cred: Credentials, timeOut: Duration = 10 minutes)(handleResponseBody: String => Unit ) = { + def upload(uri: String, file: File, cred: Credentials, timeOut: Duration = 10.minutes)(handleResponseBody: String => Unit): Unit = { val absFile = file.getAbsoluteFile() try { - val request = dispatchUrl(uri).PUT.as(cred.user,cred.pw).setBody(absFile).setContentType("application/octet-stream","UTF-8") - val r = http(request OK { response => handleResponseBody(response.getResponseBody) }) - Await.result(r, timeOut) + val request = Gigahorse.url(uri).put(absFile) + .withAuth(cred.user, cred.pw) + .addHeaders("User-Agent" -> userAgent) + .withContentType("application/octet-stream") + .withRequestTimeout(timeOut) + val body = Await.result(http.run(request, Gigahorse.asString), timeOut) + handleResponseBody(body) } catch { - case e:Exception => - throw new Exception("Error uploading " + absFile.getPath() + " to " + uri, e) + case e: Exception => + throw new Exception("Error uploading " + absFile.getPath() + " to " + uri, e) } } diff --git a/indexmeta/src/main/scala/com/typesafe/dbuild/manifest/ModuleInfo.scala b/indexmeta/src/main/scala/com/typesafe/dbuild/manifest/ModuleInfo.scala index 72f5341a9..0d0329a89 100644 --- a/indexmeta/src/main/scala/com/typesafe/dbuild/manifest/ModuleInfo.scala +++ b/indexmeta/src/main/scala/com/typesafe/dbuild/manifest/ModuleInfo.scala @@ -1,5 +1,8 @@ package com.typesafe.dbuild.manifest +import io.circe.generic.semiauto.{ deriveEncoder, deriveDecoder } +import io.circe.{ Encoder, Decoder } + /** * Represents the information for a given Ivy module within the Lightbend Reactive Platform (v1, no longer in use) * @@ -16,6 +19,10 @@ case class ModuleInfo( name: String, version: String, attributes: ModuleAttributes) +object ModuleInfo { + implicit val moduleInfoEncoder: Encoder[ModuleInfo] = deriveEncoder[ModuleInfo] + implicit val moduleInfoDecoder: Decoder[ModuleInfo] = deriveDecoder[ModuleInfo] +} /** This represents the cross-building components of modules which will be used during * resolution. @@ -41,3 +48,10 @@ case class ModuleInfo( * */ case class ModuleAttributes(scalaVersion: Option[String], sbtVersion: Option[String]) +object ModuleAttributes { + implicit val moduleAttributesEncoder: Encoder[ModuleAttributes] = Encoder.AsObject.instance { a => + io.circe.JsonObject.fromIterable( + deriveEncoder[ModuleAttributes].encodeObject(a).toIterable.filterNot(_._2.isNull)) + } + implicit val moduleAttributesDecoder: Decoder[ModuleAttributes] = deriveDecoder[ModuleAttributes] +} diff --git a/logging/src/main/scala/com/typesafe/dbuild/logging/Logger.scala b/logging/src/main/scala/com/typesafe/dbuild/logging/Logger.scala index b1dd473fc..524c850de 100644 --- a/logging/src/main/scala/com/typesafe/dbuild/logging/Logger.scala +++ b/logging/src/main/scala/com/typesafe/dbuild/logging/Logger.scala @@ -1,11 +1,11 @@ package com.typesafe.dbuild.logging -import scala.sys.process.ProcessLogger -import sbt.dbuild.hack.DbuildHack.ExceptionCategory import com.typesafe.dbuild.graph -import com.typesafe.dbuild.adapter.{LoggingInterface,StreamLoggerAdapter} -import LoggingInterface.{Logger=>SbtLogger,BasicLogger=>SbtBasicLogger,_} -import LoggingInterface.Level._ +import sbt.dbuild.hack.DbuildHack.ExceptionCategory +import sbt.internal.util.{ BasicLogger => SbtBasicLogger, StackTrace } +import sbt.util.{ Logger => SbtLogger, LogEvent, ControlEvent, Level } +import Level.* +import scala.sys.process.ProcessLogger trait Logger extends SbtLogger with ProcessLogger { def newNestedLogger(name: String, projName: String = ""): Logger @@ -51,7 +51,7 @@ object ConsoleLogger { abstract class BasicLogger extends SbtBasicLogger with Logger /** Logs to an output stream. */ -final class StreamLogger(out: java.io.PrintStream, debug: Boolean) extends BasicLogger with StreamLoggerAdapter { +final class StreamLogger(out: java.io.PrintStream, debug: Boolean) extends BasicLogger { if (debug) setLevel(Debug) diff --git a/metadata/src/main/scala-2.12/CirceDerivationCompat.scala b/metadata/src/main/scala-2.12/CirceDerivationCompat.scala new file mode 100644 index 000000000..438a4cb1e --- /dev/null +++ b/metadata/src/main/scala-2.12/CirceDerivationCompat.scala @@ -0,0 +1,20 @@ +package com.typesafe.dbuild.model + +import io.circe.generic.extras.Configuration +import io.circe.{ Encoder, Decoder } + +/** + * A stable-named `deriveEncoder`/`deriveDecoder` pair, backed on Scala 2.12 by + * circe-generic-extras (which supports Scala default parameter values as fallbacks + * for missing JSON fields, unlike plain circe-generic). See the scala-3 variant of + * this file for the Scala 3 counterpart, backed by circe-core's own native + * io.circe.derivation.{ConfiguredEncoder,ConfiguredDecoder}. + */ +object CirceDerivationCompat { + implicit val circeConfig: Configuration = Configuration.default.withDefaults + + def deriveEncoder[A](implicit ev: shapeless.Lazy[io.circe.generic.extras.encoding.ConfiguredAsObjectEncoder[A]]): Encoder.AsObject[A] = + io.circe.generic.extras.semiauto.deriveEncoder[A] + def deriveDecoder[A](implicit ev: shapeless.Lazy[io.circe.generic.extras.decoding.ConfiguredDecoder[A]]): Decoder[A] = + io.circe.generic.extras.semiauto.deriveDecoder[A] +} diff --git a/metadata/src/main/scala-3/CirceDerivationCompat.scala b/metadata/src/main/scala-3/CirceDerivationCompat.scala new file mode 100644 index 000000000..8d9408748 --- /dev/null +++ b/metadata/src/main/scala-3/CirceDerivationCompat.scala @@ -0,0 +1,19 @@ +package com.typesafe.dbuild.model + +import io.circe.derivation.{ Configuration, ConfiguredEncoder, ConfiguredDecoder } +import io.circe.{ Encoder, Decoder } +import scala.deriving.Mirror + +/** + * A stable-named `deriveEncoder`/`deriveDecoder` pair, backed on Scala 3 by circe-core's + * own native io.circe.derivation.{ConfiguredEncoder,ConfiguredDecoder} (which supports + * Scala default parameter values as fallbacks for missing JSON fields). See the + * scala-2.12 variant of this file for the Scala 2.12 counterpart, backed by + * circe-generic-extras. + */ +object CirceDerivationCompat { + given Configuration = Configuration.default.withDefaults + + inline def deriveEncoder[A](using inline mirror: Mirror.Of[A]): Encoder.AsObject[A] = ConfiguredEncoder.derived[A] + inline def deriveDecoder[A](using inline mirror: Mirror.Of[A]): Decoder[A] = ConfiguredDecoder.derived[A] +} diff --git a/metadata/src/main/scala/com/typesafe/dbuild/model/Artifacts.scala b/metadata/src/main/scala/com/typesafe/dbuild/model/Artifacts.scala index 787d38a87..31e685c41 100644 --- a/metadata/src/main/scala/com/typesafe/dbuild/model/Artifacts.scala +++ b/metadata/src/main/scala/com/typesafe/dbuild/model/Artifacts.scala @@ -1,17 +1,38 @@ package com.typesafe.dbuild.model +import CirceSupport.* +import io.circe.generic.semiauto.{ deriveEncoder, deriveDecoder } +import io.circe.{ Encoder, Decoder } import java.io.File +object FileCodec { + // Jackson's built-in File serializer normalizes to the absolute path; a relative + // File written here would otherwise be resolved against the *reader's* working + // directory (a different process, generally an sbt subprocess with its own cwd), + // producing a wrong, doubled-up path. + implicit val fileEncoder: Encoder[File] = Encoder.encodeString.contramap(_.getAbsolutePath) + implicit val fileDecoder: Decoder[File] = Decoder.decodeString.map(new File(_)) +} +import FileCodec.* + /** * This currently represents a "coordinate" of an artifact, the version you must * rewire to depend on, and the amount of time it took to build such an artifact. */ case class ArtifactLocation(info: ProjectRef, version: String, crossSuffix: String, pluginAttrs: Option[SbtPluginAttrs]) +object ArtifactLocation { + implicit val artifactLocationEncoder: Encoder[ArtifactLocation] = dropNullValues(deriveEncoder[ArtifactLocation]) + implicit val artifactLocationDecoder: Decoder[ArtifactLocation] = deriveDecoder[ArtifactLocation] +} /** * If the artifact is an sbt plugin, it will have in its extraAttributes some additional information, that we will * need during rewiring. We store them here, and we set pluginAttrs in ArtifactLocation to Some(SbtPluginAttrs) accordingly. */ case class SbtPluginAttrs(sbtVersion: String, scalaVersion: String) +object SbtPluginAttrs { + implicit val sbtPluginAttrsEncoder: Encoder[SbtPluginAttrs] = deriveEncoder[SbtPluginAttrs] + implicit val sbtPluginAttrsDecoder: Decoder[SbtPluginAttrs] = deriveDecoder[SbtPluginAttrs] +} /** * This class represents an Artifact's SHA (of the file) for unique storage and the * location it has in a maven/ivy/p2 repository. @@ -19,6 +40,10 @@ case class SbtPluginAttrs(sbtVersion: String, scalaVersion: String) * We use this to push files into artifactory and retrieve them as a workaround now. */ case class ArtifactSha(sha: String, location: String) +object ArtifactSha { + implicit val artifactShaEncoder: Encoder[ArtifactSha] = deriveEncoder[ArtifactSha] + implicit val artifactShaDecoder: Decoder[ArtifactSha] = deriveDecoder[ArtifactSha] +} /** * This is the metadata a project generates after building. We can deploy this to our repository as @@ -29,6 +54,10 @@ case class ArtifactSha(sha: String, location: String) case class ProjectArtifactInfo( project: RepeatableProjectBuild, versions: BuildArtifactsOut) +object ProjectArtifactInfo { + implicit val projectArtifactInfoEncoder: Encoder[ProjectArtifactInfo] = deriveEncoder[ProjectArtifactInfo] + implicit val projectArtifactInfoDecoder: Decoder[ProjectArtifactInfo] = deriveDecoder[ProjectArtifactInfo] +} /** * This represents two pieces of data: @@ -54,6 +83,10 @@ case class ProjectArtifactInfo( * (that is done via the artifacts UUIDs): it is only used in diagnostic messages */ case class BuildArtifactsIn(artifacts: Seq[ArtifactLocation], fromSpace: String, localRepo: File) +object BuildArtifactsIn { + implicit val buildArtifactsInEncoder: Encoder[BuildArtifactsIn] = deriveEncoder[BuildArtifactsIn] + implicit val buildArtifactsInDecoder: Decoder[BuildArtifactsIn] = deriveDecoder[BuildArtifactsIn] +} // variant for multi-level build systems case class BuildArtifactsInMulti(materialized: Seq /*Levels*/ [BuildArtifactsIn]) { // to simplify single-level build systems, the following convenience methods @@ -61,7 +94,15 @@ case class BuildArtifactsInMulti(materialized: Seq /*Levels*/ [BuildArtifactsIn] def artifacts = materialized.head.artifacts def localRepo = materialized.head.localRepo } +object BuildArtifactsInMulti { + implicit val buildArtifactsInMultiEncoder: Encoder[BuildArtifactsInMulti] = deriveEncoder[BuildArtifactsInMulti] + implicit val buildArtifactsInMultiDecoder: Decoder[BuildArtifactsInMulti] = deriveDecoder[BuildArtifactsInMulti] +} case class BuildArtifactsOut(results: Seq[BuildSubArtifactsOut]) +object BuildArtifactsOut { + implicit val buildArtifactsOutEncoder: Encoder[BuildArtifactsOut] = deriveEncoder[BuildArtifactsOut] + implicit val buildArtifactsOutDecoder: Decoder[BuildArtifactsOut] = deriveDecoder[BuildArtifactsOut] +} // moduleInfo presents a view of the generated *modules*, while artifacts and shas refer to the // generated *artifacts* (files). The two are related, but not strictly: information like version // or name could differ, in theory. The two different views are carried around together as part @@ -69,6 +110,10 @@ case class BuildArtifactsOut(results: Seq[BuildSubArtifactsOut]) // artifacts and module info. case class BuildSubArtifactsOut(subName: String, artifacts: Seq[ArtifactLocation], shas: Seq[ArtifactSha], moduleInfo: com.typesafe.dbuild.manifest.ModuleInfo) +object BuildSubArtifactsOut { + implicit val buildSubArtifactsOutEncoder: Encoder[BuildSubArtifactsOut] = deriveEncoder[BuildSubArtifactsOut] + implicit val buildSubArtifactsOutDecoder: Decoder[BuildSubArtifactsOut] = deriveDecoder[BuildSubArtifactsOut] +} /** * This represents general information every dbuild must know: @@ -78,3 +123,7 @@ case class BuildSubArtifactsOut(subName: String, artifacts: Seq[ArtifactLocation * For subproj, see RepeatableProjectBuild. */ case class BuildInput(artifacts: BuildArtifactsInMulti, version: String, subproj: Seq /*Levels*/ [Seq[String]], outRepo: File, projectName: String) +object BuildInput { + implicit val buildInputEncoder: Encoder[BuildInput] = deriveEncoder[BuildInput] + implicit val buildInputDecoder: Decoder[BuildInput] = deriveDecoder[BuildInput] +} diff --git a/metadata/src/main/scala/com/typesafe/dbuild/model/BuildOutcome.scala b/metadata/src/main/scala/com/typesafe/dbuild/model/BuildOutcome.scala index 89174b198..165c5fbad 100644 --- a/metadata/src/main/scala/com/typesafe/dbuild/model/BuildOutcome.scala +++ b/metadata/src/main/scala/com/typesafe/dbuild/model/BuildOutcome.scala @@ -1,9 +1,10 @@ package com.typesafe.dbuild.model -import com.fasterxml.jackson.annotation.JsonProperty +import com.typesafe.config.ConfigFactory.parseString import com.typesafe.dbuild.model.Utils.{ readValue, writeValue } +import io.circe.generic.semiauto.deriveEncoder +import io.circe.{ Encoder, Json } import org.apache.commons.lang.StringEscapeUtils -import com.typesafe.config.ConfigFactory.parseString /** @@ -22,6 +23,21 @@ sealed abstract class BuildOutcome { /** a copy of this outcome, with replaced nested outcomes */ def withOutcomes(os:Seq[BuildOutcome]):BuildOutcome } +object BuildOutcome { + // BuildOutcome is only ever serialized (for debug printing / notification templates), never + // deserialized from JSON, so we only need an Encoder for the whole sealed hierarchy. + implicit lazy val buildOutcomeEncoder: Encoder[BuildOutcome] = Encoder.instance { + case o: BuildSuccess => deriveEncoder[BuildSuccess].apply(o) + case o: BuildUnchanged => deriveEncoder[BuildUnchanged].apply(o) + case o: BuildEmpty => deriveEncoder[BuildEmpty].apply(o) + case o: BuildFailed => deriveEncoder[BuildFailed].apply(o) + case o: BuildBrokenDependency => deriveEncoder[BuildBrokenDependency].apply(o) + case o: ExtractionOK => deriveEncoder[ExtractionOK].apply(o) + case o: ExtractionFailed => deriveEncoder[ExtractionFailed].apply(o) + case o: TaskFailed => deriveEncoder[TaskFailed].apply(o) + case o: UnexpectedOutcome => deriveEncoder[UnexpectedOutcome].apply(o) + } +} // this marker trait is only used internally, in order to // recognize outcomes that are caused by timeout conditions. diff --git a/metadata/src/main/scala/com/typesafe/dbuild/model/Config.scala b/metadata/src/main/scala/com/typesafe/dbuild/model/Config.scala index ec21d556c..79ea52c59 100644 --- a/metadata/src/main/scala/com/typesafe/dbuild/model/Config.scala +++ b/metadata/src/main/scala/com/typesafe/dbuild/model/Config.scala @@ -1,54 +1,46 @@ package com.typesafe.dbuild.model -import com.fasterxml.jackson.databind._ -import com.fasterxml.jackson.databind.annotation.JsonSerialize -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.lambdaworks.jacks._ -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.ser.std.StringSerializer -import com.fasterxml.jackson.databind.ser.impl.StringArraySerializer -import collection.JavaConverters._ +import CirceDerivationCompat.* +import CirceSupport.* import com.typesafe.dbuild.deploy.DeployTarget import com.typesafe.dbuild.hashing -import scala.concurrent.duration._ - -import SeqBooleanH._ -import SeqDBCH._ -import SeqDepsModifiersH._ -import SeqNotificationH._ -import SeqSelectorElementH._ -import SeqSeqStringH._ -import SeqStringH._ +import io.circe.{ Encoder, Decoder, Json, JsonObject } +import scala.concurrent.duration.* + +import SeqBooleanH.* +import SeqDBCH.* +import SeqDepsModifiersH.* +import SeqNotificationH.* +import SeqSelectorElementH.* +import SeqSeqStringH.* +import SeqStringH.* /** * Metadata about a build. This is extracted from a config file and contains enough information * to further extract information about a build. */ -@JsonDeserialize(using = classOf[BuildConfigDeserializer]) case class ProjectBuildConfig(name: String, system: String = "sbt", uri: String = "nil", - @JsonProperty("set-version") setVersion: Option[String], + setVersion: Option[String], // if both set-version and set-version-suffix are specified, // then set-version will take precedence - @JsonProperty("set-version-suffix") setVersionSuffix: Option[String], + setVersionSuffix: Option[String], deps: SeqDepsModifiers = Seq.empty, // the default crossVersion for ProjectBuildConfig is None: // that means the values will be taken from the enclosing // ProjectOptions record - @JsonProperty("cross-version") crossVersion: Option[Seq /*Levels*/ [String]] = None, + crossVersion: Option[Seq /*Levels*/ [String]] = None, // the default checkMissing None: works in the same manner as crossVersion - @JsonProperty("check-missing") checkMissing: Option[Seq /*Levels*/ [Boolean]] = None, + checkMissing: Option[Seq /*Levels*/ [Boolean]] = None, // the default rewriteOverrides None: works in the same manner as crossVersion - @JsonProperty("rewrite-overrides") rewriteOverrides: Option[Seq /*Levels*/ [Boolean]] = None, - @JsonProperty("use-jgit") useJGit: Option[Boolean] = None, + rewriteOverrides: Option[Seq /*Levels*/ [Boolean]] = None, + useJGit: Option[Boolean] = None, space: Option[Space] = None, extra: Option[ExtraConfig]) { // after the initial expansion // you can use getExtra() to obtain the extra content - def getExtra[T](implicit m: Manifest[T]) = extra match { - case Some(t: T) => t + def getExtra[T] = extra match { + case Some(t: T @unchecked) => t case None => sys.error("Internal error: \"extra\" has not been expanded in project " + name + ". Please report.") case _ => sys.error("Internal error: \"extra\" has the wrong type in project " + name + ". Please report.") } @@ -106,19 +98,58 @@ object RewriteOverridesDefaults { def defaults = Stream.continually(true) } -// Do keep the one above and the one below in sync -private case class ProjectBuildConfigShadow(name: String, - system: String = "sbt", - uri: String = "nil", - @JsonProperty("set-version") setVersion: Option[String], - @JsonProperty("set-version-suffix") setVersionSuffix: Option[String], - deps: SeqDepsModifiers = Seq.empty, - @JsonProperty("cross-version") crossVersion: Option[SeqString /*Levels*/ ] = None, - @JsonProperty("check-missing") checkMissing: Option[SeqBoolean /*Levels*/ ] = None, - @JsonProperty("rewrite-overrides") rewriteOverrides: Option[SeqBoolean /*Levels*/ ] = None, - @JsonProperty("use-jgit") useJGit: Option[Boolean] = None, +// Used only for decoding: mirrors ProjectBuildConfig, but with "extra" left as raw JSON, +// since which concrete ExtraConfig subtype it holds depends on the sibling "system" field. +private case class ProjectBuildConfigRaw(name: String, + system: Option[String] = None, + uri: Option[String] = None, + setVersion: Option[String], + setVersionSuffix: Option[String], + deps: Option[SeqDepsModifiers] = None, + crossVersion: Option[SeqString /*Levels*/ ] = None, + checkMissing: Option[SeqBoolean /*Levels*/ ] = None, + rewriteOverrides: Option[SeqBoolean /*Levels*/ ] = None, + useJGit: Option[Boolean] = None, space: Option[Space] = None, - extra: JsonNode = null) + extra: Option[Json] = None) +private object ProjectBuildConfigRaw { + implicit val decoder: Decoder[ProjectBuildConfigRaw] = + renamedDec( + "setVersion" -> "set-version", + "setVersionSuffix" -> "set-version-suffix", + "crossVersion" -> "cross-version", + "checkMissing" -> "check-missing", + "rewriteOverrides" -> "rewrite-overrides", + "useJGit" -> "use-jgit")(deriveDecoder[ProjectBuildConfigRaw]) +} + +object ProjectBuildConfig { + implicit val projectBuildConfigDecoder: Decoder[ProjectBuildConfig] = Decoder.instance { c => + c.as[ProjectBuildConfigRaw].flatMap { raw => + val system = raw.system.getOrElse("sbt") + ExtraConfig.decoderFor(system) match { + case None => Left(io.circe.DecodingFailure("Build system \"" + system + "\" is unknown.", c.history)) + case Some(dec) => + (raw.extra match { + case None => Right(None) + case Some(json) => dec.decodeJson(json).map(Some(_)) + }).map { newData => + ProjectBuildConfig(raw.name, system, raw.uri.getOrElse("nil"), raw.setVersion, raw.setVersionSuffix, + raw.deps.getOrElse(Nil), raw.crossVersion map { _.s }, raw.checkMissing map { _.s }, + raw.rewriteOverrides map { _.s }, raw.useJGit, raw.space, newData) + } + } + } + } + implicit val projectBuildConfigEncoder: Encoder[ProjectBuildConfig] = + dropNullValues(renamedEnc( + "setVersion" -> "set-version", + "setVersionSuffix" -> "set-version-suffix", + "crossVersion" -> "cross-version", + "checkMissing" -> "check-missing", + "rewriteOverrides" -> "rewrite-overrides", + "useJGit" -> "use-jgit")(deriveEncoder[ProjectBuildConfig])) +} case class DepsModifiers( // One or more dependencies, in the form "org#name". @@ -129,6 +160,10 @@ case class DepsModifiers( // These are dependencies as seen by dbuild (as extracted); they are not // the actual project's dependencies. inject: SeqString = Seq.empty) +object DepsModifiers { + implicit val depsModifiersEncoder: Encoder[DepsModifiers] = deriveEncoder[DepsModifiers] + implicit val depsModifiersDecoder: Decoder[DepsModifiers] = deriveDecoder[DepsModifiers] +} /** * A specification for Spaces, as used by projects. @@ -157,8 +192,6 @@ case class DepsModifiers( * a special space (to which one cannot publish), and which * means "do not rewire". */ -@JsonDeserialize(using = classOf[SpaceDeserializer]) -@JsonSerialize(using = classOf[SpaceSerializer]) case class Space(from: Seq /*Levels*/ [String], to: Seq[String]) { // We can't place "defaults" in the companion object, otherwise // the case class loses its standard facilities. So we place it here instead. @@ -171,36 +204,25 @@ case class Space(from: Seq /*Levels*/ [String], to: Seq[String]) { def fromStream = from.toStream ++ SpaceDefaults.defaults.drop(from.length) } case class SpaceAux(from: SeqString = Seq.empty, to: SeqString = Seq("default")) -class SpaceDeserializer extends JsonDeserializer[Space] { - override def deserialize(p: JsonParser, ctx: DeserializationContext): Space = { - val tf = ctx.getConfig.getTypeFactory() - val d = ctx.findContextualValueDeserializer(tf.constructType(classOf[JsonNode]), null) - val generic = d.deserialize(p, ctx).asInstanceOf[JsonNode] - val jp = generic.traverse() - jp.nextToken() - def valueAs[T](cls: Class[T]) = { - val vd = ctx.findContextualValueDeserializer(tf.constructType(cls), null) - cls.cast(vd.deserialize(jp, ctx)) - } - if (generic.isTextual()) { - val s = valueAs(classOf[String]) - new Space(s) - } else { - val aux = valueAs(classOf[SpaceAux]) - Space(aux.from, aux.to) - } - } +object SpaceAux { + implicit val spaceAuxEncoder: Encoder[SpaceAux] = deriveEncoder[SpaceAux] + implicit val spaceAuxDecoder: Decoder[SpaceAux] = deriveDecoder[SpaceAux] } -class SpaceSerializer extends JsonSerializer[Space] { - override def serialize(value: Space, g: JsonGenerator, p: SerializerProvider) { - if (value.to.length == 1 && value.to(0) == value.from) { - val vs = p.findValueSerializer(classOf[String], null) - vs.serialize(value.from, g, p) - } else { - val vs = p.findValueSerializer(classOf[SpaceAux], null) - vs.serialize(SpaceAux(value.from, value.to), g, p) +object Space { + implicit val spaceDecoder: Decoder[Space] = Decoder.instance { c => + c.value.asString match { + case Some(s) => Right(new Space(s)) + case None => c.as[SpaceAux].map(aux => Space(aux.from, aux.to)) } } + // Note: in the original Jackson-based serializer, the "single symmetric string" shorthand + // was gated on `value.to(0) == value.from` -- comparing a String to a Seq[String], which can + // never be true. That branch was therefore always dead, and Space was always written out in + // full {from:...,to:...} form. We replicate that observable behavior here (see SpaceDeserializer + // for the reverse: reading *does* correctly accept the bare-string shorthand). + implicit val spaceEncoder: Encoder[Space] = Encoder.instance { value => + SpaceAux.spaceAuxEncoder(SpaceAux(value.from, value.to)) + } } /** @@ -222,21 +244,20 @@ case class DBuildConfiguration( /** The unique SHA for this configuration */ def uuid = hashing sha1 this } +object DBuildConfiguration { + implicit val dBuildConfigurationEncoder: Encoder[DBuildConfiguration] = dropNullValues(deriveEncoder[DBuildConfiguration]) + implicit val dBuildConfigurationDecoder: Decoder[DBuildConfiguration] = deriveDecoder[DBuildConfiguration] +} /* This section is unchecked, and is used prior to deserialization by * the Typesafe config library. Its contents are no longer used once we * get to deserialization, which is why it is always replaced with an * empty record. */ -@JsonDeserialize(using = classOf[VarDeserializer]) case class Vars() -class VarDeserializer extends JsonDeserializer[Vars] { - override def deserialize(p: JsonParser, ctx: DeserializationContext): Vars = { - val tf = ctx.getConfig.getTypeFactory() - val d = ctx.findContextualValueDeserializer(tf.constructType(classOf[JsonNode]), null) - val generic = d.deserialize(p, ctx).asInstanceOf[JsonNode] - Vars() - } +object Vars { + implicit val varsEncoder: Encoder[Vars] = Encoder.instance(_ => Json.obj()) + implicit val varsDecoder: Decoder[Vars] = Decoder.instance(_ => Right(Vars())) } /** @@ -245,7 +266,11 @@ class VarDeserializer extends JsonDeserializer[Vars] { */ case class ExtractionConfig(buildConfig: ProjectBuildConfig) { def uuid = hashing sha1 this - def extra[T](implicit m: Manifest[T]) = buildConfig.getExtra[T] + def extra[T] = buildConfig.getExtra[T] +} +object ExtractionConfig { + implicit val extractionConfigEncoder: Encoder[ExtractionConfig] = deriveEncoder[ExtractionConfig] + implicit val extractionConfigDecoder: Decoder[ExtractionConfig] = deriveDecoder[ExtractionConfig] } /** @@ -298,34 +323,49 @@ case class ExtractionConfig(buildConfig: ProjectBuildConfig) { case class DBuildConfig(projects: Seq[ProjectBuildConfig], /* deprecated, see deserializer */ options: Option[DeprecatedBuildOptions], - @JsonProperty("cross-version") crossVersion: SeqString /*Levels*/ = Seq.empty, //all missing values will be "disabled" + crossVersion: SeqString /*Levels*/ = Seq.empty, //all missing values will be "disabled" // if "standard" (the default), use whatever sbt version is defined by the project. If none is defined, stop and ask for one. - @JsonProperty("check-missing") checkMissing: SeqBoolean /*Levels*/ = Seq.empty, //all missing values will be determined + checkMissing: SeqBoolean /*Levels*/ = Seq.empty, //all missing values will be determined // according to the corresponding value of cross-version: if "standard", then false, else true. - @JsonProperty("rewrite-overrides") rewriteOverrides: SeqBoolean /*Levels*/ = Seq.empty, //all missing values will be true - @JsonProperty("sbt-version") sbtVersion: String = "standard", + rewriteOverrides: SeqBoolean /*Levels*/ = Seq.empty, //all missing values will be true + sbtVersion: String = "standard", // This option applies to all sbt-based projects, unless overridden. // see SbtExtraConfig for details. - @JsonProperty("extraction-version") extractionVersion: String = "standard", + extractionVersion: String = "standard", // Select jgit rather than the command-line git. It is in the BuildOptions, // rather than in the GeneralOptions, as its value may conceivably have // an effect on building (for instance due to a difference in checkout because // of an implementation bug) - @JsonProperty("use-jgit") useJGit: Boolean = false, + useJGit: Boolean = false, // settings for sbt-based builds // Note on the default value: it must contain a single empty SeqString. Using as a // default value an empty Seq[SeqString] will result in the value obtained after // serialization and deserialization to be different, which causes troubles when // checking repository consistency. - @JsonProperty("sbt-settings") sbtSettings: SeqSeqString /* Levels */ = SeqSeqString(Seq(Seq.empty)), + sbtSettings: SeqSeqString /* Levels */ = SeqSeqString(Seq(Seq.empty)), // commands for sbt-based builds - @JsonProperty("sbt-commands") sbtCommands: SeqString = Seq.empty, + sbtCommands: SeqString = Seq.empty, // commands for sbt-based builds, to be run after compilation and test - @JsonProperty("sbt-post-commands") sbtPostCommands: SeqString = Seq.empty, + sbtPostCommands: SeqString = Seq.empty, // see javaOptions in SbtExtraConfig - @JsonProperty("sbt-java-options") sbtJavaOptions: Option[SeqString] = None, + sbtJavaOptions: Option[SeqString] = None, // Default space for regular project space: Space = new Space("default")) extends BuildOptions +object DBuildConfig { + private val renames: Seq[(String, String)] = Seq( + "crossVersion" -> "cross-version", + "checkMissing" -> "check-missing", + "rewriteOverrides" -> "rewrite-overrides", + "sbtVersion" -> "sbt-version", + "extractionVersion" -> "extraction-version", + "useJGit" -> "use-jgit", + "sbtSettings" -> "sbt-settings", + "sbtCommands" -> "sbt-commands", + "sbtPostCommands" -> "sbt-post-commands", + "sbtJavaOptions" -> "sbt-java-options") + implicit val dBuildConfigEncoder: Encoder[DBuildConfig] = dropNullValues(renamedEnc(renames: _*)(deriveEncoder[DBuildConfig])) + implicit val dBuildConfigDecoder: Decoder[DBuildConfig] = renamedDec(renames: _*)(deriveDecoder[DBuildConfig]) +} /** * General options for dbuild, that do not affect the actual build. @@ -337,11 +377,19 @@ case class GeneralOptions(deploy: Seq[DeployOptions] = Seq.empty, cleanup: CleanupOptions = CleanupOptions(), timeouts: Timeouts = Timeouts() ) +object GeneralOptions { + implicit val generalOptionsEncoder: Encoder[GeneralOptions] = deriveEncoder[GeneralOptions] + implicit val generalOptionsDecoder: Decoder[GeneralOptions] = deriveDecoder[GeneralOptions] +} // expiration times are in hours. Dirs are cleaned if the (truncated) // number of hours between now and the time in which a build was last -// attempted (the initial time) is >= than the number specified here +// attempted (the initial time) is >= than the number specified here case class CleanupExpirations(success: Int, failure: Int) +object CleanupExpirations { + implicit val cleanupExpirationsEncoder: Encoder[CleanupExpirations] = deriveEncoder[CleanupExpirations] + implicit val cleanupExpirationsDecoder: Decoder[CleanupExpirations] = deriveDecoder[CleanupExpirations] +} /** * The default maximum ages before reclaiming disk space are: * - successful build: 2 days @@ -352,14 +400,16 @@ case class CleanupExpirations(success: Int, failure: Int) case class CleanupOptions( build: CleanupExpirations = CleanupExpirations(success = 48, failure = 168), extraction: CleanupExpirations = CleanupExpirations(success = 120, failure = 168)) +object CleanupOptions { + implicit val cleanupOptionsEncoder: Encoder[CleanupOptions] = deriveEncoder[CleanupOptions] + implicit val cleanupOptionsDecoder: Decoder[CleanupOptions] = deriveDecoder[CleanupOptions] +} /** * This class acts as a useful wrapper for parameters that are Seqs of Strings: it makes it * possible to specify a simple string whenever an array of strings is expected in the JSON file. * Quite handy, really. */ -@JsonSerialize(using = classOf[SeqStringSerializer]) -@JsonDeserialize(using = classOf[SeqStringDeserializer]) case class SeqString(override val s: Seq[String]) extends Flex[String](s) { // whenever I use a SeqString to apply map or foreach, the implicit // will kick in. However, when I try to print or use it as a string, @@ -368,8 +418,10 @@ case class SeqString(override val s: Seq[String]) extends Flex[String](s) { // but, just in case: override def toString() = s.toString } -class SeqStringDeserializer extends SeqFlexDeserializer[String, SeqString] -class SeqStringSerializer extends SeqFlexSerializer[String] +object SeqString { + implicit val seqStringEncoder: Encoder[SeqString] = flexEncoder[String](Encoder.encodeString).contramap(_.s) + implicit val seqStringDecoder: Decoder[SeqString] = flexDecoder[String](Decoder.decodeString).map(SeqString(_)) +} object SeqStringH { implicit def SeqToSeqString(s: Seq[String]): SeqString = SeqString(s) implicit def SeqStringToSeq(a: SeqString): Seq[String] = a.s @@ -378,13 +430,13 @@ object SeqStringH { /** * Similar to the above, but for Booleans. */ -@JsonSerialize(using = classOf[SeqBooleanSerializer]) -@JsonDeserialize(using = classOf[SeqBooleanDeserializer]) case class SeqBoolean(override val s: Seq[Boolean]) extends Flex[Boolean](s) { override def toString() = s.toString } -class SeqBooleanDeserializer extends SeqFlexDeserializer[Boolean, SeqBoolean] -class SeqBooleanSerializer extends SeqFlexSerializer[Boolean] +object SeqBoolean { + implicit val seqBooleanEncoder: Encoder[SeqBoolean] = flexEncoder[Boolean](Encoder.encodeBoolean).contramap(_.s) + implicit val seqBooleanDecoder: Decoder[SeqBoolean] = flexDecoder[Boolean](Decoder.decodeBoolean).map(SeqBoolean(_)) +} object SeqBooleanH { implicit def SeqToSeqBoolean(s: Seq[Boolean]): SeqBoolean = SeqBoolean(s) implicit def SeqBooleanToSeq(a: SeqBoolean): Seq[Boolean] = a.s @@ -394,11 +446,11 @@ object SeqBooleanH { * Similar to the above, but for DBuildConfig elements: * a single one in the config file will automatically be turned into an array. */ -@JsonSerialize(using = classOf[SeqDBCSerializer]) -@JsonDeserialize(using = classOf[SeqDBCDeserializer]) case class SeqDBC(override val s: Seq[DBuildConfig]) extends Flex[DBuildConfig](s) -class SeqDBCDeserializer extends SeqFlexDeserializer[DBuildConfig, SeqDBC] -class SeqDBCSerializer extends SeqFlexSerializer[DBuildConfig] +object SeqDBC { + implicit val seqDBCEncoder: Encoder[SeqDBC] = flexEncoder[DBuildConfig](DBuildConfig.dBuildConfigEncoder).contramap(_.s) + implicit val seqDBCDecoder: Decoder[SeqDBC] = flexDecoder[DBuildConfig](DBuildConfig.dBuildConfigDecoder).map(SeqDBC(_)) +} object SeqDBCH { implicit def SeqToSeqDBC(s: Seq[DBuildConfig]): SeqDBC = SeqDBC(s) implicit def SeqDBCToSeq(a: SeqDBC): Seq[DBuildConfig] = a.s @@ -407,11 +459,11 @@ object SeqDBCH { /** * For DepsModifiers, we can have one modifier per level, for sbt specifically. */ -@JsonSerialize(using = classOf[SeqDMSerializer]) -@JsonDeserialize(using = classOf[SeqDMDeserializer]) case class SeqDepsModifiers(override val s: Seq[DepsModifiers]) extends Flex[DepsModifiers](s) -class SeqDMDeserializer extends SeqFlexDeserializer[DepsModifiers, SeqDepsModifiers] -class SeqDMSerializer extends SeqFlexSerializer[DepsModifiers] +object SeqDepsModifiers { + implicit val seqDepsModifiersEncoder: Encoder[SeqDepsModifiers] = flexEncoder[DepsModifiers](DepsModifiers.depsModifiersEncoder).contramap(_.s) + implicit val seqDepsModifiersDecoder: Decoder[SeqDepsModifiers] = flexDecoder[DepsModifiers](DepsModifiers.depsModifiersDecoder).map(SeqDepsModifiers(_)) +} object SeqDepsModifiersH { implicit def SeqToSeqDM(s: Seq[DepsModifiers]): SeqDepsModifiers = SeqDepsModifiers(s) implicit def SeqDMToSeq(a: SeqDepsModifiers): Seq[DepsModifiers] = a.s @@ -424,8 +476,6 @@ object SeqDepsModifiersH { * or directly an array of array of Strings (which becomes directly a Seq(Seq())). * It is only used by sbtSettings, currently. */ -@JsonSerialize(using = classOf[SeqSeqStringSerializer]) -@JsonDeserialize(using = classOf[SeqSeqStringDeserializer]) case class SeqSeqString(override val s: Seq[SeqString]) extends Flex[SeqString](s) { // turn the SeqSeqString into a Seq[Seq[String]] def expand = s map {_.s} @@ -435,7 +485,7 @@ object SeqSeqStringH { implicit def SeqSeqStringToSeq(a: SeqSeqString): Seq[SeqString] = a.s } -// Note: it is not enough to extends SeqFlexSerializer[SeqString]. +// Note: it is not enough to encode/decode as a plain Flex[SeqString]. // For instance, in the case in which the object to be deserialized // is SeqSeqString(List(List(aaa), List(bbb))), just deserializing // using Flex would lead to a List of two SeqString, which would @@ -446,45 +496,30 @@ object SeqSeqStringH { // more than one element, we skip the SeqString and convert it // directly to a Seq[Seq[String]], which is then deserialized // into the JSON representation [["aaa"],["bbb"]]. -class SeqSeqStringSerializer extends JsonSerializer[SeqSeqString] { - override def serialize(value: SeqSeqString, g: JsonGenerator, p: SerializerProvider) { +object SeqSeqString { + implicit val seqSeqStringEncoder: Encoder[SeqSeqString] = Encoder.instance { value => value.s.length match { - case 1 => - val vs = p.findValueSerializer(classOf[SeqString], null) - vs.serialize(value.s(0), g, p) - case _ => - val vs = p.findValueSerializer(classOf[Seq[Seq[String]]], null) - vs.serialize(value.s map {_.s}, g, p) + case 1 => SeqString.seqStringEncoder(value.s(0)) + case _ => Json.arr(value.s.map(ss => flexEncoder[String](Encoder.encodeString)(ss.s)): _*) } } -} - -// Flex cannot cope with the special case of SeqSeqString deserialization; we write a custom one. -class SeqSeqStringDeserializer extends JsonDeserializer[SeqSeqString] { - override def deserialize(p: JsonParser, ctx: DeserializationContext): SeqSeqString = { - val tf = ctx.getConfig.getTypeFactory() - val d = ctx.findContextualValueDeserializer(tf.constructType(classOf[JsonNode]), null) - val generic = d.deserialize(p, ctx).asInstanceOf[JsonNode] - val jp = generic.traverse() - jp.nextToken() - def valueAs[T](cls: Class[T]) = { - val vd = ctx.findContextualValueDeserializer(tf.constructType(cls), null) - cls.cast(vd.deserialize(jp, ctx)) - } - // If generic is already an array of arrays, do not wrap, else wrap; - // since we might have stuff like [ x, [ y,z ]], we need to traverse - // the whole generic to look for arrays of arrays. - val needsWrapping = if (generic.isArray()) { - !generic.iterator().asScala.exists(_.isArray) - } else { - true - } - if (needsWrapping) { - SeqSeqString(Seq(valueAs(classOf[SeqString]))) - } else { - // this valueAs() will return a WrappedArray; with the latest sha fixes, - // it should have the same sha as a default Seq (a List) - SeqSeqString(valueAs(classOf[Array[SeqString]])) + // Flex cannot cope with the special case of SeqSeqString deserialization; we write a custom one. + implicit val seqSeqStringDecoder: Decoder[SeqSeqString] = Decoder.instance { c => + c.value.asArray match { + case Some(arr) => + // If generic is already an array of arrays, do not wrap, else wrap; + // since we might have stuff like [ x, [ y,z ]], we need to traverse + // the whole generic to look for arrays of arrays. + val needsWrapping = !arr.exists(_.isArray) + if (needsWrapping) { + c.as[SeqString].map(ss => SeqSeqString(Seq(ss))) + } else { + arr.foldLeft(Right(Vector.empty): Decoder.Result[Vector[SeqString]]) { (acc, j) => + for { v <- acc; s <- flexDecoder[String](Decoder.decodeString).decodeJson(j) } yield v :+ SeqString(s) + }.map(v => SeqSeqString(v.toSeq)) + } + case None => + c.as[SeqString].map(ss => SeqSeqString(Seq(ss))) } } } @@ -493,39 +528,6 @@ class SeqSeqStringDeserializer extends JsonDeserializer[SeqSeqString] { * The generic auto-wrapping magic */ class Flex[T](val s: Seq[T]) -class SeqFlexDeserializer[T, ST <: Flex[T]](implicit m: Manifest[T], ms: Manifest[ST]) extends JsonDeserializer[ST] { - override def deserialize(p: JsonParser, ctx: DeserializationContext): ST = { - val tf = ctx.getConfig.getTypeFactory() - val d = ctx.findContextualValueDeserializer(tf.constructType(classOf[JsonNode]), null) - val generic = d.deserialize(p, ctx).asInstanceOf[JsonNode] - val jp = generic.traverse() - jp.nextToken() - def valueAs[T](cls: Class[T])(implicit m: Manifest[T]) = { - val vd = ctx.findContextualValueDeserializer(tf.constructType(cls), null) - vd.deserialize(jp, ctx).asInstanceOf[T] - } - val constructor = ms.erasure.asInstanceOf[Class[ST]].getConstructor(classOf[Seq[T]]) - if (generic.isArray()) { - // The valueAs() returns an Array; we use its values to build a new Seq. - constructor.newInstance(Seq(valueAs(m.arrayManifest.erasure.asInstanceOf[Class[Array[T]]]): _*)) - } else { - // Retrieve the single value, and wrap it into a Seq() - constructor.newInstance(Seq(valueAs[T](m.erasure.asInstanceOf[Class[T]]))) - } - } -} -class SeqFlexSerializer[T](implicit m: Manifest[T]) extends JsonSerializer[Flex[T]] { - override def serialize(value: Flex[T], g: JsonGenerator, p: SerializerProvider) { - value.s.length match { - case 1 => - val vs = p.findValueSerializer(m.erasure.asInstanceOf[Class[T]], null) - vs.serialize(value.s(0).asInstanceOf[AnyRef], g, p) - case _ => - val vs = p.findValueSerializer(m.arrayManifest.erasure.asInstanceOf[Class[Array[T]]], null) - vs.serialize(value.s.toArray, g, p) - } - } -} /** Deploy information. */ case class DeployOptions( @@ -539,8 +541,16 @@ case class DeployOptions( sign: Option[DeploySignOptions], /** index generation options */ index: Option[IndexOptions]) extends DeployTarget +object DeployOptions { + implicit val deployOptionsEncoder: Encoder[DeployOptions] = dropNullValues(deriveEncoder[DeployOptions]) + implicit val deployOptionsDecoder: Decoder[DeployOptions] = deriveDecoder[DeployOptions] +} /** used to select subprojects from one project */ case class SubProjects(from: String, subprojects: SeqString) +object SubProjects { + implicit val subProjectsEncoder: Encoder[SubProjects] = deriveEncoder[SubProjects] + implicit val subProjectsDecoder: Decoder[SubProjects] = deriveDecoder[SubProjects] +} /** * Signing options. @@ -549,9 +559,15 @@ case class SubProjects(from: String, subprojects: SeqString) * passphrase is the path to the file containing the passphrase; there is no interactive option. */ case class DeploySignOptions( - @JsonProperty("secret-ring") secretRing: Option[String], + secretRing: Option[String], id: Option[String], passphrase: String) +object DeploySignOptions { + implicit val deploySignOptionsEncoder: Encoder[DeploySignOptions] = + dropNullValues(renamedEnc("secretRing" -> "secret-ring")(deriveEncoder[DeploySignOptions])) + implicit val deploySignOptionsDecoder: Decoder[DeploySignOptions] = + renamedDec("secretRing" -> "secret-ring")(deriveDecoder[DeploySignOptions]) +} /** * Index generation options. @@ -568,6 +584,10 @@ case class IndexOptions( /** path to the credentials file */ credentials: Option[String], filename: String) extends DeployTarget +object IndexOptions { + implicit val indexOptionsEncoder: Encoder[IndexOptions] = dropNullValues(deriveEncoder[IndexOptions]) + implicit val indexOptionsDecoder: Decoder[IndexOptions] = deriveDecoder[IndexOptions] +} /** Comparison information. */ case class ComparisonOptions( @@ -575,51 +595,40 @@ case class ComparisonOptions( b: SeqSelectorElement = Seq(), skip: SeqString = Seq()) // skip is a sequence of regex patterns, // files inside the jars whose name match them will not be compared. +object ComparisonOptions { + implicit val comparisonOptionsEncoder: Encoder[ComparisonOptions] = deriveEncoder[ComparisonOptions] + implicit val comparisonOptionsDecoder: Decoder[ComparisonOptions] = deriveDecoder[ComparisonOptions] +} /** * Configuration used for SBT and other builds. */ -@JsonSerialize(using = classOf[ExtraSerializer]) class ExtraConfig -class ExtraSerializer extends JsonSerializer[ExtraConfig] { - override def serialize(value: ExtraConfig, g: JsonGenerator, p: SerializerProvider) { - val cfg = p.getConfig() - val tf = cfg.getTypeFactory() - val jt = tf.constructType(value.getClass) - ScalaTypeSig(cfg.getTypeFactory, jt) match { - case Some(sts) if sts.isCaseClass => - // the "true" below is for options.caseClassSkipNulls - (new CaseClassSerializer(jt, sts.annotatedAccessors(cfg), true)).serialize(value.asInstanceOf[Product], g, p) - case _ => throw new Exception("Internal error while serializing build system config. Please report.") - } +object ExtraConfig { + private val decoders: Map[String, Decoder[? <: ExtraConfig]] = Map( + "sbt" -> Decoder[SbtExtraConfig], + "scala" -> Decoder[ScalaExtraConfig], + "ivy" -> Decoder[IvyExtraConfig], + "assemble" -> Decoder[AssembleExtraConfig], + "aether" -> Decoder[AetherExtraConfig], + "test" -> Decoder[TestExtraConfig], + "nil" -> Decoder[NilExtraConfig]) + def decoderFor(system: String): Option[Decoder[ExtraConfig]] = + decoders.get(system).map(_.asInstanceOf[Decoder[ExtraConfig]]) + + implicit val extraConfigEncoder: Encoder[ExtraConfig] = Encoder.instance { + case e: SbtExtraConfig => ExtraConfigEncoders.sbtExtraConfigEncoder(e) + case e: ScalaExtraConfig => ExtraConfigEncoders.scalaExtraConfigEncoder(e) + case e: IvyExtraConfig => ExtraConfigEncoders.ivyExtraConfigEncoder(e) + case e: AssembleExtraConfig => ExtraConfigEncoders.assembleExtraConfigEncoder(e) + case e: AetherExtraConfig => ExtraConfigEncoders.aetherExtraConfigEncoder(e) + case e: TestExtraConfig => ExtraConfigEncoders.testExtraConfigEncoder(e) + case e: NilExtraConfig => ExtraConfigEncoders.nilExtraConfigEncoder(e) + case other => throw new Exception("Internal error while serializing build system config. Please report. (" + other.getClass + ")") } } -class BuildConfigDeserializer extends JsonDeserializer[ProjectBuildConfig] { - override def deserialize(p: JsonParser, ctx: DeserializationContext): ProjectBuildConfig = { - val buildSystems = BuildSystemExtras.buildSystems - - val tf = ctx.getConfig.getTypeFactory() - val d = ctx.findContextualValueDeserializer(tf.constructType(classOf[ProjectBuildConfigShadow]), null) - val generic = d.deserialize(p, ctx).asInstanceOf[ProjectBuildConfigShadow] - - if (generic == null) throw new Exception("Cannot deserialize build configuration: no value found") - - val from = generic.extra - val system = generic.system - if (!(buildSystems.contains(system))) throw new Exception("Build system \"" + system + "\" is unknown.") - val newData = if (from == null) None else Some({ - val cls = buildSystems(system) - val jp = from.traverse() - jp.nextToken() - cls.cast(ctx.findContextualValueDeserializer(tf.constructType(cls), null).deserialize(jp, ctx)) - }) - ProjectBuildConfig(generic.name, system, generic.uri, generic.setVersion, generic.setVersionSuffix, - generic.deps, generic.crossVersion map { _.s }, generic.checkMissing map { _.s }, - generic.rewriteOverrides map { _.s }, generic.useJGit, generic.space, newData) - } -} /** * The 'extra' options for the Scala build system are: * build-number: Overwrites the standard build.number, with a custom number @@ -639,64 +648,72 @@ class BuildConfigDeserializer extends JsonDeserializer[ProjectBuildConfig] { * All fields are optional. */ case class ScalaExtraConfig( - @JsonProperty("build-number") buildNumber: Option[BuildNumber], + buildNumber: Option[BuildNumber], // deploy-target and build-target have been replaced by "targets" - @JsonProperty("build-target") buildTarget: Option[String], - @JsonProperty("deploy-target") deployTarget: Option[String], + buildTarget: Option[String], + deployTarget: Option[String], // TODO: eventually remove the two old options above // "targets" is a list of pairs, where the first component is the target // and the second is the path relative to the root where the target should be run targets: Seq[(String, String)] = Seq.empty, - @JsonProperty("build-options") buildOptions: SeqString = Seq.empty, + buildOptions: SeqString = Seq.empty, exclude: SeqString = Seq.empty // if empty -> exclude no projects (default) ) extends ExtraConfig case class BuildNumber(major: String, minor: String, patch: String, bnum: String) +object BuildNumber { + implicit val buildNumberEncoder: Encoder[BuildNumber] = deriveEncoder[BuildNumber] + implicit val buildNumberDecoder: Decoder[BuildNumber] = deriveDecoder[BuildNumber] +} case class IvyExtraConfig( sources: Boolean = false, javadoc: Boolean = false, - @JsonProperty("main-jar") mainJar: Boolean = true, + mainJar: Boolean = true, artifacts: Seq[IvyArtifact] = Seq.empty, // The snapshot marker is used internally by the Ivy build system // in order to distinguish among different snapshots of the same // dependency, in which case it contains the publication date. // Note: this field is not for use by end user. - @JsonProperty("snapshot-marker") snapshotMarker: Option[String]) extends ExtraConfig + snapshotMarker: Option[String]) extends ExtraConfig case class AetherExtraConfig( sources: Boolean = false, javadoc: Boolean = false, - @JsonProperty("main-jar") mainJar: Boolean = true, - @JsonProperty("snapshot-marker") snapshotMarker: Option[String]) extends ExtraConfig + mainJar: Boolean = true, + snapshotMarker: Option[String]) extends ExtraConfig case class IvyArtifact( classifier: String = "", - @JsonProperty("type") typ: String = "jar", + typ: String = "jar", ext: String = "jar", configs: SeqString = Seq("default")) +object IvyArtifact { + implicit val ivyArtifactEncoder: Encoder[IvyArtifact] = renamedEnc("typ" -> "type")(deriveEncoder[IvyArtifact]) + implicit val ivyArtifactDecoder: Decoder[IvyArtifact] = renamedDec("typ" -> "type")(deriveDecoder[IvyArtifact]) +} /** * sbt-specific build parameters */ case class SbtExtraConfig( // None is interpreted as default: use build.sbt-version - @JsonProperty("sbt-version") sbtVersion: Option[String] = None, + sbtVersion: Option[String] = None, directory: String = "", - @JsonProperty("run-tests") runTests: Boolean = true, - @JsonProperty("skip-missing-tests") skipMissingTests: Boolean = false, - @JsonProperty("test-tasks") testTasks: SeqString = Seq("test"), + runTests: Boolean = true, + skipMissingTests: Boolean = false, + testTasks: SeqString = Seq("test"), // For the difference between the build section's "javaOptions", // the project-specific "options", and javaAllOptions (below), // please refer to SbtBuildSystem.expandExtra() options: SeqString = Seq.empty, - @JsonProperty("project-specific-all-java-options-combined") javaAllOptions: SeqString = Seq.empty, + javaAllOptions: SeqString = Seq.empty, // before rewiring, append these settings settings: SeqSeqString = SeqSeqString(Seq(Seq.empty)), /*Levels*/ // before building, run these commands ("set" or others) commands: SeqString = Seq.empty, // after building and testing, run these commands - @JsonProperty("post-commands") postCommands: SeqString = Seq.empty, + postCommands: SeqString = Seq.empty, projects: SeqString = Seq.empty, // if empty -> build all projects (default) exclude: SeqString = Seq.empty, // if empty -> exclude no projects (default) /** @@ -704,10 +721,65 @@ case class SbtExtraConfig( * or a version string to force a different Scala compiler. */ // None is interpreted as default: use build.extraction-version - @JsonProperty("extraction-version") extractionVersion: Option[String] = None) extends ExtraConfig + extractionVersion: Option[String] = None) extends ExtraConfig + +private object ExtraConfigEncoders { + val scalaExtraConfigRenames: Seq[(String, String)] = Seq( + "buildNumber" -> "build-number", "buildTarget" -> "build-target", + "deployTarget" -> "deploy-target", "buildOptions" -> "build-options") + implicit val scalaExtraConfigEncoder: Encoder[ScalaExtraConfig] = + dropNullValues(renamedEnc(scalaExtraConfigRenames: _*)(deriveEncoder[ScalaExtraConfig])) + implicit val scalaExtraConfigDecoder: Decoder[ScalaExtraConfig] = + renamedDec(scalaExtraConfigRenames: _*)(deriveDecoder[ScalaExtraConfig]) + + implicit val ivyExtraConfigEncoder: Encoder[IvyExtraConfig] = + dropNullValues(renamedEnc("mainJar" -> "main-jar", "snapshotMarker" -> "snapshot-marker")(deriveEncoder[IvyExtraConfig])) + implicit val ivyExtraConfigDecoder: Decoder[IvyExtraConfig] = + renamedDec("mainJar" -> "main-jar", "snapshotMarker" -> "snapshot-marker")(deriveDecoder[IvyExtraConfig]) + + implicit val aetherExtraConfigEncoder: Encoder[AetherExtraConfig] = + dropNullValues(renamedEnc("mainJar" -> "main-jar", "snapshotMarker" -> "snapshot-marker")(deriveEncoder[AetherExtraConfig])) + implicit val aetherExtraConfigDecoder: Decoder[AetherExtraConfig] = + renamedDec("mainJar" -> "main-jar", "snapshotMarker" -> "snapshot-marker")(deriveDecoder[AetherExtraConfig]) + + val sbtExtraConfigRenames: Seq[(String, String)] = Seq( + "sbtVersion" -> "sbt-version", "runTests" -> "run-tests", + "skipMissingTests" -> "skip-missing-tests", "testTasks" -> "test-tasks", + "javaAllOptions" -> "project-specific-all-java-options-combined", + "postCommands" -> "post-commands", "extractionVersion" -> "extraction-version") + implicit val sbtExtraConfigEncoder: Encoder[SbtExtraConfig] = + dropNullValues(renamedEnc(sbtExtraConfigRenames: _*)(deriveEncoder[SbtExtraConfig])) + implicit val sbtExtraConfigDecoder: Decoder[SbtExtraConfig] = + renamedDec(sbtExtraConfigRenames: _*)(deriveDecoder[SbtExtraConfig]) + + implicit val nilExtraConfigEncoder: Encoder[NilExtraConfig] = deriveEncoder[NilExtraConfig] + implicit val nilExtraConfigDecoder: Decoder[NilExtraConfig] = deriveDecoder[NilExtraConfig] + implicit val testExtraConfigEncoder: Encoder[TestExtraConfig] = deriveEncoder[TestExtraConfig] + implicit val testExtraConfigDecoder: Decoder[TestExtraConfig] = deriveDecoder[TestExtraConfig] + implicit val assembleExtraConfigEncoder: Encoder[AssembleExtraConfig] = deriveEncoder[AssembleExtraConfig] + implicit val assembleExtraConfigDecoder: Decoder[AssembleExtraConfig] = deriveDecoder[AssembleExtraConfig] +} +import ExtraConfigEncoders.* + +object ScalaExtraConfig { + implicit val scalaExtraConfigEncoder: Encoder[ScalaExtraConfig] = ExtraConfigEncoders.scalaExtraConfigEncoder + implicit val scalaExtraConfigDecoder: Decoder[ScalaExtraConfig] = ExtraConfigEncoders.scalaExtraConfigDecoder +} +object IvyExtraConfig { + implicit val ivyExtraConfigEncoder: Encoder[IvyExtraConfig] = ExtraConfigEncoders.ivyExtraConfigEncoder + implicit val ivyExtraConfigDecoder: Decoder[IvyExtraConfig] = ExtraConfigEncoders.ivyExtraConfigDecoder +} +object AetherExtraConfig { + implicit val aetherExtraConfigEncoder: Encoder[AetherExtraConfig] = ExtraConfigEncoders.aetherExtraConfigEncoder + implicit val aetherExtraConfigDecoder: Decoder[AetherExtraConfig] = ExtraConfigEncoders.aetherExtraConfigDecoder +} +object SbtExtraConfig { + implicit val sbtExtraConfigEncoder: Encoder[SbtExtraConfig] = ExtraConfigEncoders.sbtExtraConfigEncoder + implicit val sbtExtraConfigDecoder: Decoder[SbtExtraConfig] = ExtraConfigEncoders.sbtExtraConfigDecoder +} object BuildSystemExtras { - val buildSystems: Map[String, java.lang.Class[_ <: ExtraConfig]] = Map( + val buildSystems: Map[String, java.lang.Class[? <: ExtraConfig]] = Map( "sbt" -> classOf[SbtExtraConfig], "scala" -> classOf[ScalaExtraConfig], "ivy" -> classOf[IvyExtraConfig], @@ -719,17 +791,27 @@ object BuildSystemExtras { /** configuration for the Nil build system */ case class NilExtraConfig() extends ExtraConfig +object NilExtraConfig { + implicit val nilExtraConfigEncoder: Encoder[NilExtraConfig] = ExtraConfigEncoders.nilExtraConfigEncoder + implicit val nilExtraConfigDecoder: Decoder[NilExtraConfig] = ExtraConfigEncoders.nilExtraConfigDecoder +} /** configuration for the Test build system */ case class TestExtraConfig() extends ExtraConfig +object TestExtraConfig { + implicit val testExtraConfigEncoder: Encoder[TestExtraConfig] = ExtraConfigEncoders.testExtraConfigEncoder + implicit val testExtraConfigDecoder: Decoder[TestExtraConfig] = ExtraConfigEncoders.testExtraConfigDecoder +} /** configuration for the Assemble build system */ case class AssembleExtraConfig( parts: SeqDBC = Seq()) extends ExtraConfig +object AssembleExtraConfig { + implicit val assembleExtraConfigEncoder: Encoder[AssembleExtraConfig] = ExtraConfigEncoders.assembleExtraConfigEncoder + implicit val assembleExtraConfigDecoder: Decoder[AssembleExtraConfig] = ExtraConfigEncoders.assembleExtraConfigDecoder +} // our simplified version of Either: we use it to group String and SelectorSubProjects in a transparent manner -@JsonSerialize(using = classOf[SelectorElementSerializer]) -@JsonDeserialize(using = classOf[SelectorElementDeserializer]) sealed abstract class SelectorElement { def name: String } case class SelectorProject(a: String) extends SelectorElement { override def toString() = a @@ -740,40 +822,24 @@ case class SelectorSubProjects(info: SubProjects) extends SelectorElement { def name = info.from } -class SelectorElementSerializer extends JsonSerializer[SelectorElement] { - override def serialize(value: SelectorElement, g: JsonGenerator, p: SerializerProvider) { - value match { - case SelectorProject(s) => - new StringSerializer().serialize(s, g, p) - case SelectorSubProjects(d) => - val cfg = p.getConfig() - val tf = cfg.getTypeFactory() - val jt = tf.constructType(classOf[SubProjects]) - val Some(sts) = ScalaTypeSig(cfg.getTypeFactory, jt) - // the "true" below is for options.caseClassSkipNulls - (new CaseClassSerializer(jt, sts.annotatedAccessors(cfg), true)).serialize(d, g, p) - case _ => throw new Exception("Internal error while serializing deploy projects. Please report.") - } +object SelectorElement { + implicit val selectorElementEncoder: Encoder[SelectorElement] = Encoder.instance { + case SelectorProject(s) => Json.fromString(s) + case SelectorSubProjects(d) => SubProjects.subProjectsEncoder(d) } -} -class SelectorElementDeserializer extends JsonDeserializer[SelectorElement] { - override def deserialize(p: JsonParser, ctx: DeserializationContext): SelectorElement = { - val tf = ctx.getConfig.getTypeFactory() - val d = ctx.findContextualValueDeserializer(tf.constructType(classOf[JsonNode]), null) - val generic = d.deserialize(p, ctx).asInstanceOf[JsonNode] - val jp = generic.traverse() - jp.nextToken() - def valueAs[T](cls: Class[T]) = cls.cast(ctx.findContextualValueDeserializer(tf.constructType(cls), null).deserialize(jp, ctx)) - if (generic.isTextual()) { - SelectorProject(valueAs(classOf[String])) - } else { - // We have renamed "publish" to "subprojects", which is a bit more generic; - // we can use SelectorElements in more contexts. So, in order to assist with - // the migration, let's see if a "publish" field was encountered. - if (generic.fieldNames.asScala.contains("publish")) { - sys.error("In the subproject selection, the field \"publish\" is now called \"subprojects\": please update your build file.") - } - SelectorSubProjects(valueAs(classOf[SubProjects])) + implicit val selectorElementDecoder: Decoder[SelectorElement] = Decoder.instance { c => + c.value.asString match { + case Some(s) => Right(SelectorProject(s)) + case None => + // We have renamed "publish" to "subprojects", which is a bit more generic; + // we can use SelectorElements in more contexts. So, in order to assist with + // the migration, let's see if a "publish" field was encountered. + c.value.asObject match { + case Some(obj) if obj.contains("publish") => + Left(io.circe.DecodingFailure( + "In the subproject selection, the field \"publish\" is now called \"subprojects\": please update your build file.", c.history)) + case _ => c.as[SubProjects].map(SelectorSubProjects(_)) + } } } } @@ -782,8 +848,6 @@ class SelectorElementDeserializer extends JsonDeserializer[SelectorElement] { * same as SeqString, for Seq[SelectorElement]: a lonely String or a lonely * SelectorSubProjs can also be used when a Seq[SelectorElement] is requested. */ -@JsonSerialize(using = classOf[SeqElementSerializer]) -@JsonDeserialize(using = classOf[SeqElementDeserializer]) case class SeqSelectorElement(override val s: Seq[SelectorElement]) extends Flex[SelectorElement](s) { /** * From its list of selected projects, which may include '.' for the root, and @@ -808,7 +872,7 @@ case class SeqSelectorElement(override val s: Seq[SelectorElement]) extends Flex val fromRoot = if (projReqs.exists(_.name == ".")) allProjNames else Set[String]() // list of names of projects mentioned in subprojects from root - val fromDotSubs = subProjReqs.filter(_.name == ".").flatMap { p: SelectorSubProjects => p.info.subprojects } + val fromDotSubs = subProjReqs.filter(_.name == ".").flatMap { (p: SelectorSubProjects) => p.info.subprojects } // are you kidding me? if (fromDotSubs.contains(".")) sys.error("A from/publish defined '.' as a subproject of '.', which is impossible. Please amend.") // ok, this is the complete list of full project requests @@ -826,22 +890,26 @@ case class SeqSelectorElement(override val s: Seq[SelectorElement]) extends Flex reqs } } -class SeqElementDeserializer extends SeqFlexDeserializer[SelectorElement, SeqSelectorElement] -class SeqElementSerializer extends SeqFlexSerializer[SelectorElement] +object SeqSelectorElement { + implicit val seqSelectorElementEncoder: Encoder[SeqSelectorElement] = + flexEncoder[SelectorElement](SelectorElement.selectorElementEncoder).contramap(_.s) + implicit val seqSelectorElementDecoder: Decoder[SeqSelectorElement] = + flexDecoder[SelectorElement](SelectorElement.selectorElementDecoder).map(SeqSelectorElement(_)) +} object SeqSelectorElementH { implicit def SeqToSeqSelectorElement(s: Seq[SelectorElement]): SeqSelectorElement = SeqSelectorElement(s) implicit def SeqSelectorElementToSeq(a: SeqSelectorElement): Seq[SelectorElement] = a.s } /** * same as SeqString, for Seq[Notification]: a single Notification will be wrapped into an array. - * Note that I cannot make easily a generic version, since Jackson requires a no-args constructur - * and I cannot construct a sequence without a Manifest. So I only special case a few of them. */ -@JsonSerialize(using = classOf[SeqNotificationSerializer]) -@JsonDeserialize(using = classOf[SeqNotificationDeserializer]) case class SeqNotification(override val s: Seq[Notification]) extends Flex[Notification](s) -class SeqNotificationDeserializer extends SeqFlexDeserializer[Notification, SeqNotification] -class SeqNotificationSerializer extends SeqFlexSerializer[Notification] +object SeqNotification { + implicit val seqNotificationEncoder: Encoder[SeqNotification] = + flexEncoder[Notification](Notification.notificationEncoder).contramap(_.s) + implicit val seqNotificationDecoder: Decoder[SeqNotification] = + flexDecoder[Notification](Notification.notificationDecoder).map(SeqNotification(_)) +} object SeqNotificationH { implicit def SeqToSeqNotification(s: Seq[Notification]): SeqNotification = SeqNotification(s) implicit def SeqNotificationToSeq(a: SeqNotification): Seq[Notification] = a.s @@ -865,12 +933,14 @@ trait ProjectOptions { } abstract class BuildOptions extends ExtraOptions with ProjectOptions -@JsonDeserialize(using = classOf[DeprecatedBuildOptionsDeserializer]) abstract class DeprecatedBuildOptions -class DeprecatedBuildOptionsDeserializer extends JsonDeserializer[BuildOptions] { - override def deserialize(p: JsonParser, ctx: DeserializationContext): BuildOptions = { +object DeprecatedBuildOptions { + implicit val deprecatedBuildOptionsDecoder: Decoder[DeprecatedBuildOptions] = Decoder.instance { c => sys.error("\"build.options\" have moved. Please rename \"build.options.xxx\" to just \"build.xxx\".") } + // Never actually holds an instance (the field is always None in practice, since the decoder above + // always throws instead of ever producing one); only needed so Encoder[DBuildConfig] can be derived. + implicit val deprecatedBuildOptionsEncoder: Encoder[DeprecatedBuildOptions] = Encoder.instance(_ => Json.Null) } /** @@ -888,6 +958,10 @@ case class NotificationOptions( * records. */ default: SeqNotification = Seq[Notification]()) +object NotificationOptions { + implicit val notificationOptionsEncoder: Encoder[NotificationOptions] = deriveEncoder[NotificationOptions] + implicit val notificationOptionsDecoder: Decoder[NotificationOptions] = deriveDecoder[NotificationOptions] +} /** * A notification template; for notification systems that require short messages, * use only the subject line. It is a template because variable @@ -909,6 +983,10 @@ case class NotificationTemplate( summary: String, short: Option[String] = None, long: Option[String] = None) +object NotificationTemplate { + implicit val notificationTemplateEncoder: Encoder[NotificationTemplate] = dropNullValues(deriveEncoder[NotificationTemplate]) + implicit val notificationTemplateDecoder: Decoder[NotificationTemplate] = deriveDecoder[NotificationTemplate] +} /** * The NotificationTemplate is first resolved against the notification, @@ -920,8 +998,11 @@ case class ResolvedTemplate( summary: String, short: String, long: String) +object ResolvedTemplate { + implicit val resolvedTemplateEncoder: Encoder[ResolvedTemplate] = deriveEncoder[ResolvedTemplate] + implicit val resolvedTemplateDecoder: Decoder[ResolvedTemplate] = deriveDecoder[ResolvedTemplate] +} -@JsonDeserialize(using = classOf[NotificationDeserializer]) case class Notification( /** the kind of notification. Default is "email" */ kind: String = "email", @@ -953,19 +1034,6 @@ case class Notification( * if a single string is specified. */ projects: SeqSelectorElement = Seq(SelectorProject("."))) { - /* - * example: - - options.notifications.send = [{ - projects: jline - send.to: "antonio.cunei@typesafe.com" - },{ - projects: scala-compiler - send.to: "joshua.suereth@typesafe.com" - }] - options.notifications.defaults.send.smtp.server = "relay.lightbend.com" - -*/ /** * If the notification refers to a specific template name, use that template name, * otherwise the template name we need is the same as the notification kind. @@ -990,14 +1058,35 @@ case class Notification( ResolvedTemplate(templ.id, templ.summary, short, long) } } -// We need this shadow class for serialization/deserialization to work -// It must be kept in sync with Notification. -private case class NotificationShadow( +// We need this shadow class for deserialization to work: "send" needs to be decoded +// according to the sibling "kind" field, so we decode it as raw JSON first. +private case class NotificationRaw( kind: String = "email", - send: JsonNode = null, + send: Option[Json] = None, when: SeqString = Seq("bad", "success"), template: Option[String] = None, projects: SeqSelectorElement = Seq(SelectorProject("."))) +private object NotificationRaw { + implicit val decoder: Decoder[NotificationRaw] = deriveDecoder[NotificationRaw] +} +object Notification { + implicit val notificationEncoder: Encoder[Notification] = dropNullValues(deriveEncoder[Notification]) + implicit val notificationDecoder: Decoder[Notification] = Decoder.instance { c => + c.as[NotificationRaw].flatMap { raw => + val kind = raw.kind + NotificationKind.decoderFor(kind) match { + case None => Left(io.circe.DecodingFailure("Notification kind \"" + kind + "\" is unknown.", c.history)) + case Some(dec) => + (raw.send match { + case None => Right(None) + case Some(json) => dec.decodeJson(json).map(Some(_)) + }).map { newData => + Notification(kind, newData, raw.when, raw.template, raw.projects) + } + } + } + } +} /** * The descriptor of options for each notification mechanism; @@ -1007,57 +1096,25 @@ private case class NotificationShadow( * notifications; any inappropriate default value should be * detected when sending (or before). */ -@JsonSerialize(using = classOf[NotificationKindSerializer]) abstract class NotificationKind -class NotificationKindSerializer extends JsonSerializer[NotificationKind] { - override def serialize(value: NotificationKind, g: JsonGenerator, p: SerializerProvider) { - val cfg = p.getConfig() - val tf = cfg.getTypeFactory() - val jt = tf.constructType(value.getClass) - ScalaTypeSig(cfg.getTypeFactory, jt) match { - case Some(sts) if sts.isCaseClass => - // the "true" below is for options.caseClassSkipNulls - (new CaseClassSerializer(jt, sts.annotatedAccessors(cfg), true)).serialize(value.asInstanceOf[Product], g, p) - case _ => throw new Exception("Internal error while serializing NotificationKind. Please report.") - } - } -} - -class NotificationDeserializer extends JsonDeserializer[Notification] { - override def deserialize(p: JsonParser, ctx: DeserializationContext): Notification = { - val notificationKinds = NotificationKindH.kinds - - val tf = ctx.getConfig.getTypeFactory() - val d = ctx.findContextualValueDeserializer(tf.constructType(classOf[NotificationShadow]), null) - val generic = d.deserialize(p, ctx).asInstanceOf[NotificationShadow] - - if (generic == null) throw new Exception("Cannot deserialize notification: no value found") - - val from = generic.send - val kind = generic.kind - - if (!(notificationKinds.contains(kind))) throw new Exception("Notification kind \"" + kind + "\" is unknown.") - val newData = if (from == null) None else Some({ - val cls = notificationKinds(kind) - val jp = from.traverse() - jp.nextToken() - cls.cast(ctx.findContextualValueDeserializer(tf.constructType(cls), null).deserialize(jp, ctx)) - }) - Notification(kind, newData, generic.when, generic.template, generic.projects) +object NotificationKind { + private val decoders: Map[String, Decoder[? <: NotificationKind]] = Map( + "console" -> Decoder[ConsoleNotification], + "flowdock" -> Decoder[FlowdockNotification], + "email" -> Decoder[EmailNotification]) + def decoderFor(kind: String): Option[Decoder[NotificationKind]] = + decoders.get(kind).map(_.asInstanceOf[Decoder[NotificationKind]]) + + implicit val notificationKindEncoder: Encoder[NotificationKind] = Encoder.instance { + case n: ConsoleNotification => ConsoleNotification.consoleNotificationEncoder(n) + case n: FlowdockNotification => FlowdockNotification.flowdockNotificationEncoder(n) + case n: EmailNotification => EmailNotification.emailNotificationEncoder(n) + case other => throw new Exception("Internal error while serializing NotificationKind. Please report. (" + other.getClass + ")") } } -/** - * We could embed send() into NotificationKind, but: - * 1) too much logic would end up in the low-level metadata package - * 2) we would not have access to higher-level context - * Therefore the actual send() is implemented in a twin class higher up in dbuild. - * We also offer an opportunity to the NotificationKinds to do something once before all the - * notifications are sent, and something afterward. Only the notification kinds that - * are actually used in the configuration file will be called. - */ -abstract class NotificationContext[T <: NotificationKind](implicit m: Manifest[T]) { +abstract class NotificationContext[T <: NotificationKind](implicit ct: scala.reflect.ClassTag[T]) { /** before() is called once after the build, before all the send()s of this kind. */ def before() = {} /** after() is called after all the send()s of this kind, for cleanup. */ @@ -1088,25 +1145,18 @@ abstract class NotificationContext[T <: NotificationKind](implicit m: Manifest[T n match { case None => send(defaultOptions, templ, outcome) case Some(no) => - // NotificationKinds are referred to by String IDs, so we have to check manually - // (the code must work on 2.9 as well) - if (m.erasure.isInstance(no)) send(no.asInstanceOf[T], templ, outcome) else - sys.error("Internal error: " + this.getClass.getName + " received a " + n.getClass.getName + ". Please report.") + if (ct.runtimeClass.isInstance(no)) send(no.asInstanceOf[T], templ, outcome) else + sys.error("Internal error: " + this.getClass.getName + " received a " + no.getClass.getName + ". Please report.") } } /** The client code calls mergeOptionsK, which is internally re-dispatched to mergeOptions */ def mergeOptionsK(over: Option[NotificationKind], under: Option[NotificationKind]): NotificationKind = { - // Once again, I get something that is no more specific than a NotificationKind, but I *know* from a String ID - // that they are the right kind for this Context, so I test for extra safety and cast. I am sure there is a - // prettier solution, probably using type members or path-dependent types (by nesting NotificationKind and/or - // NotificationContext into Notification), but implementation time is short at the moment and for the moment this will do. - // TODO: clean up this uglyness (over, under) match { case (None, None) => defaultOptions case (Some(ov), None) => ov case (None, Some(un)) => un case (Some(ov), Some(un)) => - if (m.erasure.isInstance(ov) && m.erasure.isInstance(un)) mergeOptions(ov.asInstanceOf[T], un.asInstanceOf[T]) else + if (ct.runtimeClass.isInstance(ov) && ct.runtimeClass.isInstance(un)) mergeOptions(ov.asInstanceOf[T], un.asInstanceOf[T]) else sys.error("Internal error: " + this.getClass.getName + " received: " + ov.getClass.getName + "-" + un.getClass.getName + ". Please report.") } } @@ -1131,6 +1181,10 @@ case class EmailNotification( * to localhost, port 25, no auth, hoping for the best. */ smtp: Smtp = Smtp("localhost", None, "none", false)) extends NotificationKind +object EmailNotification { + implicit val emailNotificationEncoder: Encoder[EmailNotification] = dropNullValues(deriveEncoder[EmailNotification]) + implicit val emailNotificationDecoder: Decoder[EmailNotification] = deriveDecoder[EmailNotification] +} /** * Messages sent to a Flowdock flow, via their Push API @@ -1151,6 +1205,10 @@ case class FlowdockNotification( from: String = "", /** tags that will be appended to the message */ tags: SeqString = Seq.empty) extends NotificationKind +object FlowdockNotification { + implicit val flowdockNotificationEncoder: Encoder[FlowdockNotification] = deriveEncoder[FlowdockNotification] + implicit val flowdockNotificationDecoder: Decoder[FlowdockNotification] = deriveDecoder[FlowdockNotification] +} /** * Description of the smtp server to be used for email delivery. @@ -1179,12 +1237,22 @@ case class Smtp( * If using SSL/TLS, a self-signed certificate could be in use. * In that case, explicitly disable certificate checking here. */ - @JsonProperty("check-certificate") checkCertificate: Boolean = true) + checkCertificate: Boolean = true) +object Smtp { + implicit val smtpEncoder: Encoder[Smtp] = + dropNullValues(renamedEnc("checkCertificate" -> "check-certificate")(deriveEncoder[Smtp])) + implicit val smtpDecoder: Decoder[Smtp] = + renamedDec("checkCertificate" -> "check-certificate")(deriveDecoder[Smtp]) +} case class ConsoleNotification() extends NotificationKind +object ConsoleNotification { + implicit val consoleNotificationEncoder: Encoder[ConsoleNotification] = deriveEncoder[ConsoleNotification] + implicit val consoleNotificationDecoder: Decoder[ConsoleNotification] = deriveDecoder[ConsoleNotification] +} object NotificationKindH { - val kinds: Map[String, java.lang.Class[_ <: NotificationKind]] = Map( + val kinds: Map[String, java.lang.Class[? <: NotificationKind]] = Map( "console" -> classOf[ConsoleNotification], "flowdock" -> classOf[FlowdockNotification], "email" -> classOf[EmailNotification]) @@ -1194,34 +1262,28 @@ object NotificationKindH { case class Timeouts ( // timeout that we allow for each extraction to complete // (may include git/svn checkout, and Ivy resolution) - @JsonProperty("extraction") extractionTimeout: FiniteDuration = 1.hour, + extractionTimeout: FiniteDuration = 1.hour, // timeout that we allow for each build to complete (only during the build phase); - @JsonProperty("build") buildTimeout: FiniteDuration = 5.hours, + buildTimeout: FiniteDuration = 5.hours, // timeout that we allow for the entire extraction phase to complete - @JsonProperty("extraction-phase") extractionPhaseTimeout: FiniteDuration = 6.hours, + extractionPhaseTimeout: FiniteDuration = 6.hours, // timeout that we allow for the entire build phase to complete - @JsonProperty("build-phase") buildPhaseTimeout: FiniteDuration = 16.hours, + buildPhaseTimeout: FiniteDuration = 16.hours, // overall timeout for the entire dbuild to complete - @JsonProperty("dbuild") dbuildTimeout: FiniteDuration = 23.hours + dbuildTimeout: FiniteDuration = 23.hours ) - -class FiniteDurationSerializer extends JsonSerializer[FiniteDuration] { - override def serialize(value: FiniteDuration, g: JsonGenerator, p: SerializerProvider) { - new StringSerializer().serialize(value.toString, g, p) - } -} -class FiniteDurationDeserializer extends JsonDeserializer[FiniteDuration] { - override def deserialize(p: JsonParser, ctx: DeserializationContext): FiniteDuration = { - val tf = ctx.getConfig.getTypeFactory() - val d = ctx.findContextualValueDeserializer(tf.constructType(classOf[String]), null) - val s = d.deserialize(p, ctx).asInstanceOf[String] - Duration(s) match { - case f: FiniteDuration if f > Duration("0 nanoseconds") => f - case _ => sys.error("The duration "+s+" is invalid, it must be a positive, non-zero finite duration"); - } - } +object Timeouts { + import Utils.{ finiteDurationEncoder, finiteDurationDecoder } + private val renames: Seq[(String, String)] = Seq( + "extractionTimeout" -> "extraction", + "buildTimeout" -> "build", + "extractionPhaseTimeout" -> "extraction-phase", + "buildPhaseTimeout" -> "build-phase", + "dbuildTimeout" -> "dbuild") + implicit val timeoutsEncoder: Encoder[Timeouts] = renamedEnc(renames: _*)(deriveEncoder[Timeouts]) + implicit val timeoutsDecoder: Decoder[Timeouts] = renamedDec(renames: _*)(deriveDecoder[Timeouts]) } diff --git a/metadata/src/main/scala/com/typesafe/dbuild/model/Extracted.scala b/metadata/src/main/scala/com/typesafe/dbuild/model/Extracted.scala index a0fa40845..34db64004 100644 --- a/metadata/src/main/scala/com/typesafe/dbuild/model/Extracted.scala +++ b/metadata/src/main/scala/com/typesafe/dbuild/model/Extracted.scala @@ -1,9 +1,7 @@ package com.typesafe.dbuild.model -import com.fasterxml.jackson.annotation.{ JsonCreator, JsonProperty } - -// Note: all of the case classes in this file are serialized/deserialized -// by using the "jacks" library. For that library to work correctly, there -// must not be any companion objects defined for those case classes. +import CirceDerivationCompat.* +import CirceSupport.* +import io.circe.{ Encoder, Decoder } /** * A project dep is an extracted *external* build dependency. I.e. this is a @@ -16,6 +14,10 @@ case class ProjectRef( classifier: Option[String] = None) { override def toString = organization + ":" + name + ":" + (classifier map (_ + ":") getOrElse "") + extension } +object ProjectRef { + implicit val projectRefEncoder: Encoder[ProjectRef] = dropNullValues(deriveEncoder[ProjectRef]) + implicit val projectRefDecoder: Decoder[ProjectRef] = deriveDecoder[ProjectRef] +} /** * Represents extracted Project information in a build. A project is akin to a @@ -26,6 +28,10 @@ case class Project( organization: String, artifacts: Seq[ProjectRef], dependencies: Seq[ProjectRef]) +object Project { + implicit val projectEncoder: Encoder[Project] = deriveEncoder[Project] + implicit val projectDecoder: Decoder[Project] = deriveDecoder[Project] +} /** * Describes the project and dependency information of a project. @@ -35,6 +41,10 @@ case class Project( * The subproj list can be empty for build systems that do not support subprojects. */ case class ProjMeta(version: String, projects: Seq[Project], subproj: Seq[String] = Seq.empty) +object ProjMeta { + implicit val projMetaEncoder: Encoder[ProjMeta] = deriveEncoder[ProjMeta] + implicit val projMetaDecoder: Decoder[ProjMeta] = deriveDecoder[ProjMeta] +} /** * Represents the *Extracted* metadata of a build. @@ -51,9 +61,7 @@ case class ProjMeta(version: String, projects: Seq[Project], subproj: Seq[String * plugins of the plugins (if any), and so on. * "version" and "projects" of the levels above the first are of no particular use. */ -case class ExtractedBuildMeta(@JsonProperty("proj-info") projInfo: Seq /*Levels*/ [ProjMeta]) { - // do NOT define secondary constructors, otherwise the Jacks/Jackson library may get quite confused - +case class ExtractedBuildMeta(projInfo: Seq /*Levels*/ [ProjMeta]) { def getHead = (projInfo.headOption getOrElse sys.error("Internal Error: Empty ProjInfo in ExtractedBuildMeta")) // compatibility, only base level def subproj = getHead.subproj @@ -74,6 +82,12 @@ case class ExtractedBuildMeta(@JsonProperty("proj-info") projInfo: Seq /*Levels* "%s -> (%s, %s, %s)" format (index, version, projects.mkString("\n\t", "\n\t", "\n"), subproj.mkString("\n ", ", ", "\n")) }) } +object ExtractedBuildMeta { + implicit val extractedBuildMetaEncoder: Encoder[ExtractedBuildMeta] = + renamedEnc("projInfo" -> "proj-info")(deriveEncoder[ExtractedBuildMeta]) + implicit val extractedBuildMetaDecoder: Decoder[ExtractedBuildMeta] = + renamedDec("projInfo" -> "proj-info")(deriveDecoder[ExtractedBuildMeta]) +} object ExtractedBuildMetaH { def apply(version: String, projects: Seq[Project], subproj: Seq[String] = Seq.empty): ExtractedBuildMeta = ExtractedBuildMeta(Seq /*Levels*/ (ProjMeta(version, projects, subproj))) diff --git a/metadata/src/main/scala/com/typesafe/dbuild/model/GraphSupport.scala b/metadata/src/main/scala/com/typesafe/dbuild/model/GraphSupport.scala index fe4efd309..ef77cae63 100644 --- a/metadata/src/main/scala/com/typesafe/dbuild/model/GraphSupport.scala +++ b/metadata/src/main/scala/com/typesafe/dbuild/model/GraphSupport.scala @@ -1,5 +1,5 @@ package com.typesafe.dbuild.model -import com.typesafe.dbuild.graph._ +import com.typesafe.dbuild.graph.* case class BuildNode(value: ProjectConfigAndExtracted) extends Node[ProjectConfigAndExtracted] { /** @@ -30,18 +30,18 @@ case class BuildEdge(from: Node[ProjectConfigAndExtracted], to: BuildNode, } class BuildGraph(builds: Seq[ProjectConfigAndExtracted]) extends Graph[ProjectConfigAndExtracted, EdgeData] { - private val buildNodes = builds.map(b => new BuildNode(b))(collection.breakOut) + private val buildNodes = builds.map(b => new BuildNode(b)) override val nodes: Set[Nd] = buildNodes.toSet private val nodeMap: Map[ProjectConfigAndExtracted, Node[ProjectConfigAndExtracted]] = - buildNodes.map(b => b.value -> b)(collection.breakOut) + buildNodes.map(b => b.value -> b).toMap def nodeFor(build: ProjectConfigAndExtracted): Option[Nd] = nodeMap.get(build) def nodeForName(name: String): Option[Nd] = nodes find (_.value.config.name == name) // Memoized ok? def edges(n: Nd): Seq[Ed] = edgeMap get n getOrElse Seq.empty private val edgeMap: Map[Nd, Seq[Ed]] = - buildNodes.map(n => n -> edgesImpl(n))(collection.breakOut) + buildNodes.map(n => n -> edgesImpl(n)).toMap private def edgesImpl(n: Nd): Seq[Ed] = { def projMetaWithSpaces(e: ProjectConfigAndExtracted) = { // associate each "from" space with the dependencies of that level diff --git a/metadata/src/main/scala/com/typesafe/dbuild/model/RepeatableBuild.scala b/metadata/src/main/scala/com/typesafe/dbuild/model/RepeatableBuild.scala index befa1a97e..929f3cc80 100644 --- a/metadata/src/main/scala/com/typesafe/dbuild/model/RepeatableBuild.scala +++ b/metadata/src/main/scala/com/typesafe/dbuild/model/RepeatableBuild.scala @@ -1,9 +1,11 @@ package com.typesafe.dbuild.model +import CirceSupport.{ *, given } import Utils.{ writeValue, canSeeSpace } -import com.fasterxml.jackson.annotation.JsonProperty import com.typesafe.dbuild.hashing -import com.typesafe.dbuild.model.SeqStringH._ +import com.typesafe.dbuild.model.SeqStringH.* +import io.circe.generic.semiauto.{ deriveEncoder, deriveDecoder } +import io.circe.{ Encoder, Decoder } /** * Information on how to build a project. Consists of both dbuild @@ -13,10 +15,19 @@ import com.typesafe.dbuild.model.SeqStringH._ * Note that the global build options are not included, therefore this is not * the entire information that can guarantee a unique build. */ +/** Common trait for metadata classes that carry a repeatability UUID, used by LocalRepoHelper.publishMeta. */ +trait HasUuid { + def uuid: String +} + case class ProjectConfigAndExtracted(config: ProjectBuildConfig, extracted: ExtractedBuildMeta) { // in theory space should never be None def getSpace = config.space getOrElse sys.error("Internal error: space is None in " + config.name) } +object ProjectConfigAndExtracted { + implicit val projectConfigAndExtractedEncoder: Encoder[ProjectConfigAndExtracted] = deriveEncoder[ProjectConfigAndExtracted] + implicit val projectConfigAndExtractedDecoder: Decoder[ProjectConfigAndExtracted] = deriveDecoder[ProjectConfigAndExtracted] +} /** * This class represents *ALL* IMMUTABLE information about a project such @@ -38,16 +49,20 @@ case class ProjectConfigAndExtracted(config: ProjectBuildConfig, extracted: Extr */ case class RepeatableProjectBuild(configAndExtracted: ProjectConfigAndExtracted, // see below the description of RepeatableDepInfo for details - depInfo: Seq /*Levels*/ [RepeatableDepInfo]) { + depInfo: Seq /*Levels*/ [RepeatableDepInfo]) extends HasUuid { /** UUID for this project. */ def uuid = hashing sha1 this // shortcut to the ProjectBuildConfig def config = configAndExtracted.config - def extra[T](implicit m: Manifest[T]) = configAndExtracted.config.getExtra[T] + def extra[T] = configAndExtracted.config.getExtra[T] def getCommit = configAndExtracted.config.getCommit } +object RepeatableProjectBuild { + implicit val repeatableProjectBuildEncoder: Encoder[RepeatableProjectBuild] = deriveEncoder[RepeatableProjectBuild] + implicit val repeatableProjectBuildDecoder: Decoder[RepeatableProjectBuild] = deriveDecoder[RepeatableProjectBuild] +} /** * This structure contains information related to each level of a multi-level @@ -56,7 +71,7 @@ case class RepeatableProjectBuild(configAndExtracted: ProjectConfigAndExtracted, * levels reflect the fromStream sequence in the project space descriptor. */ case class RepeatableDepInfo( - @JsonProperty("base-version") baseVersion: String, + baseVersion: String, // The list of dependencies is transitive (within the boundaries // of the relevant spaces, see below) dependencyNames: Seq[String], // names corresponding to a RepeatableProjectBuild @@ -66,6 +81,12 @@ case class RepeatableDepInfo( // assumption anywhere that they should be kept in sync. If that need should arise, // the two Seqs should probably be converted into a Seq[(String,String)]. ) +object RepeatableDepInfo { + implicit val repeatableDepInfoEncoder: Encoder[RepeatableDepInfo] = + renamedEnc("baseVersion" -> "base-version")(deriveEncoder[RepeatableDepInfo]) + implicit val repeatableDepInfoDecoder: Decoder[RepeatableDepInfo] = + renamedDec("baseVersion" -> "base-version")(deriveDecoder[RepeatableDepInfo]) +} object RepeatableDBuildConfigH { def fromExtractionOutcome(outcome: ExtractionOK) = RepeatableDBuildConfig(outcome.pces) @@ -75,6 +96,10 @@ object RepeatableDBuildConfigH { * Also known as the repeatable config. Note that notifications * are not included, as they have no effect on builds. */ +object RepeatableDBuildConfig { + implicit val repeatableDBuildConfigEncoder: Encoder[RepeatableDBuildConfig] = deriveEncoder[RepeatableDBuildConfig] + implicit val repeatableDBuildConfigDecoder: Decoder[RepeatableDBuildConfig] = deriveDecoder[RepeatableDBuildConfig] +} case class RepeatableDBuildConfig(builds: Seq[ProjectConfigAndExtracted]) { def repeatableBuildConfig = DBuildConfig(builds map (_.config), options = None) /** The unique SHA for this build. */ @@ -146,7 +171,9 @@ case class RepeatableDBuildConfig(builds: Seq[ProjectConfigAndExtracted]) { // this could probably be further optimized, // but hopefully the collision sets are of modest size for { - List(one, two) <- origins.combinations(2) + pair <- origins.combinations(2).toSeq + one = pair(0) + two = pair(1) colliding <- Utils.collidingSeqSpaces(one.spaces, two.spaces) } yield (org, name, one.fromProject, two.fromProject, colliding) } @@ -162,6 +189,10 @@ case class RepeatableDBuildConfig(builds: Seq[ProjectConfigAndExtracted]) { } // This is the structure that is saved in meta/build -case class SavedConfiguration(expandedDBuildConfig: DBuildConfiguration, fullBuild: RepeatableDBuildConfig) { +case class SavedConfiguration(expandedDBuildConfig: DBuildConfiguration, fullBuild: RepeatableDBuildConfig) extends HasUuid { def uuid = hashing sha1 this } +object SavedConfiguration { + implicit val savedConfigurationEncoder: Encoder[SavedConfiguration] = deriveEncoder[SavedConfiguration] + implicit val savedConfigurationDecoder: Decoder[SavedConfiguration] = deriveDecoder[SavedConfiguration] +} diff --git a/metadata/src/main/scala/com/typesafe/dbuild/model/TemplateFormatter.scala b/metadata/src/main/scala/com/typesafe/dbuild/model/TemplateFormatter.scala index d8e51a2e7..38e3ee767 100644 --- a/metadata/src/main/scala/com/typesafe/dbuild/model/TemplateFormatter.scala +++ b/metadata/src/main/scala/com/typesafe/dbuild/model/TemplateFormatter.scala @@ -1,22 +1,42 @@ package com.typesafe.dbuild.model -import com.fasterxml.jackson.annotation.JsonProperty +import CirceSupport.* +import com.typesafe.config.ConfigFactory.parseString import com.typesafe.dbuild.model.Utils.{ readValue, writeValue } +import io.circe.generic.semiauto.{ deriveEncoder, deriveDecoder } +import io.circe.{ Encoder, Decoder } import org.apache.commons.lang.StringEscapeUtils -import com.typesafe.config.ConfigFactory.parseString /** * Utility classes, used to perform variable substitutions */ -case class SubstitutionNotifications(@JsonProperty("template-vars") vars: SubstitutionVars) +case class SubstitutionNotifications(vars: SubstitutionVars) +object SubstitutionNotifications { + private val renames = Seq("vars" -> "template-vars") + implicit val substitutionNotificationsEncoder: Encoder[SubstitutionNotifications] = + renamedEnc(renames: _*)(deriveEncoder[SubstitutionNotifications]) + implicit val substitutionNotificationsDecoder: Decoder[SubstitutionNotifications] = + renamedDec(renames: _*)(deriveDecoder[SubstitutionNotifications]) +} case class SubstitutionVars( - @JsonProperty("project-name") projectName: String, - @JsonProperty("subprojects-report") subprojectsReport: String, - @JsonProperty("subprojects-report-tabs") subprojectsReportTabs: String, - @JsonProperty("project-description") projectDescription: String, - @JsonProperty("padded-project-description") paddedProjectDescription: String, - @JsonProperty("config-name") configName: String, + projectName: String, + subprojectsReport: String, + subprojectsReportTabs: String, + projectDescription: String, + paddedProjectDescription: String, + configName: String, status: String) +object SubstitutionVars { + private val renames = Seq( + "projectName" -> "project-name", + "subprojectsReport" -> "subprojects-report", + "subprojectsReportTabs" -> "subprojects-report-tabs", + "projectDescription" -> "project-description", + "paddedProjectDescription" -> "padded-project-description", + "configName" -> "config-name") + implicit val substitutionVarsEncoder: Encoder[SubstitutionVars] = renamedEnc(renames: _*)(deriveEncoder[SubstitutionVars]) + implicit val substitutionVarsDecoder: Decoder[SubstitutionVars] = renamedDec(renames: _*)(deriveDecoder[SubstitutionVars]) +} // they become: ${notifications.vars.project-name}, etc. /** @@ -28,7 +48,7 @@ class TemplateFormatter(templ: ResolvedTemplate, outcome: BuildOutcome, confName lazy val subprojectsReport: String = { // def get(o: BuildOutcome) = new TemplateFormatter(templ, o, confName).summary // val s = outcome.outcomes.map(get) - val s = outcome.outcomes.sortBy(_.project.toLowerCase).map{o=>paddedProjectDescription(o)+": "+o.status} + val s = outcome.outcomes.sortBy(_.project.toLowerCase).map{o=>paddedProjectDescription(o)+": "+o.status()} if (s.isEmpty) "" else s.mkString("", "\n", "\n") } def paddedProjectDescription(outcome:BuildOutcome) = @@ -39,7 +59,7 @@ class TemplateFormatter(templ: ResolvedTemplate, outcome: BuildOutcome, confName SubstitutionVars(projectName = outcome.project, subprojectsReport = subprojectsReport, subprojectsReportTabs = subprojectsReport.split("\n").mkString("\t","\n\t","\n"), - status = outcome.status, + status = outcome.status(), projectDescription = if (outcome.project != ".") "project " + outcome.project else "dbuild", paddedProjectDescription = paddedProjectDescription(outcome), configName = if (confName.endsWith(".dbuild")) confName.substring(0, confName.length-7) else confName)) diff --git a/metadata/src/main/scala/com/typesafe/dbuild/model/Utils.scala b/metadata/src/main/scala/com/typesafe/dbuild/model/Utils.scala index bef3fb564..de1421e0c 100644 --- a/metadata/src/main/scala/com/typesafe/dbuild/model/Utils.scala +++ b/metadata/src/main/scala/com/typesafe/dbuild/model/Utils.scala @@ -1,59 +1,94 @@ package com.typesafe.dbuild.model import ClassLoaderMadness.withContextLoader +import com.typesafe.config.Config import com.typesafe.config.ConfigException.Missing import com.typesafe.config.ConfigFactory.{ parseString, parseFile } import com.typesafe.config.ConfigRenderOptions -import com.typesafe.config.Config -import com.lambdaworks.jacks.JacksOption._ -import com.lambdaworks.jacks.JacksMapper -import com.fasterxml.jackson.databind._ -import com.fasterxml.jackson.databind.module.SimpleModule -import scala.concurrent.duration._ +import io.circe.parser.parse +import io.circe.{ Decoder, Encoder, Json, JsonObject, ParsingFailure, DecodingFailure } import java.io.File +import scala.concurrent.duration.* + +/** Thrown on a JSON parsing/decoding failure; kept under this name for compatibility with callers + * that used to catch Jackson's JsonMappingException. */ +class JsonMappingException(message: String, cause: Throwable) extends RuntimeException(message, cause) { + def this(message: String) = this(message, null) +} + +/** + * Small helpers used to replicate, on top of Circe, the exact wire conventions the old + * jacks/Jackson-based serialization relied on: + * - fields renamed via a kebab-case JSON key (formerly @JsonProperty(...)). + * - Option fields that are entirely omitted from the JSON object when None + * (formerly CaseClassSkipNulls(true)), rather than written as `null`. + * - the "Flex" single-value-or-array encoding used by the various SeqXxx wrapper types. + */ +object CirceSupport { + /** Renames JSON object keys on the way out (Scala field name -> JSON name). */ + def renamedEnc[A](pairs: (String, String)*)(enc: Encoder.AsObject[A]): Encoder.AsObject[A] = + Encoder.AsObject.instance { a => + val map = pairs.toMap + JsonObject.fromIterable(enc.encodeObject(a).toIterable.map { case (k, v) => (map.getOrElse(k, k), v) }) + } + /** Renames JSON object keys on the way in (JSON name -> Scala field name), the reverse of renamedEnc. */ + def renamedDec[A](pairs: (String, String)*)(dec: Decoder[A]): Decoder[A] = { + val reverse = pairs.map(_.swap).toMap + dec.prepare(_.withFocus(_.mapObject { obj => + JsonObject.fromIterable(obj.toIterable.map { case (k, v) => (reverse.getOrElse(k, k), v) }) + })) + } + + /** Drops JSON-null-valued keys entirely, instead of writing them out as `null`. */ + def dropNullValues[A](enc: Encoder.AsObject[A]): Encoder.AsObject[A] = + Encoder.AsObject.instance(a => JsonObject.fromIterable(enc.encodeObject(a).toIterable.filterNot(_._2.isNull))) + + /** The "Flex" encoding: a Seq of exactly one element is written as a bare value, not a one-element array. */ + def flexEncoder[T](enc: Encoder[T]): Encoder[Seq[T]] = Encoder.instance { + case Seq(single) => enc(single) + case s => Json.arr(s.map(enc.apply): _*) + } + /** The "Flex" decoding: a bare value becomes a one-element Seq; a JSON array decodes elementwise. */ + def flexDecoder[T](dec: Decoder[T]): Decoder[Seq[T]] = Decoder.instance { c => + c.value.asArray match { + case Some(arr) => + arr.foldLeft(Right(Vector.empty): Decoder.Result[Vector[T]]) { (acc, j) => + for { v <- acc; t <- dec.decodeJson(j) } yield v :+ t + }.map(_.toSeq) + case None => dec.decodeJson(c.value).map(Seq(_)) + } + } +} object Utils { - private val mapper = JacksMapper.withOptions(CaseClassCheckNulls(true), - CaseClassSkipNulls(true), CaseClassRequireKnown(true)) - private val module = new SimpleModule() - module.addSerializer(classOf[FiniteDuration], new FiniteDurationSerializer()) - module.addDeserializer(classOf[FiniteDuration], new FiniteDurationDeserializer()) - mapper.mapper.registerModule(module) - - def readValueT[T](c: Config)(implicit m: Manifest[T]) = + import CirceSupport.* + + implicit val finiteDurationEncoder: Encoder[FiniteDuration] = Encoder.encodeString.contramap(_.toString) + implicit val finiteDurationDecoder: Decoder[FiniteDuration] = Decoder.decodeString.emap { s => + Duration(s) match { + case f: FiniteDuration if f > Duration("0 nanoseconds") => Right(f) + case _ => Left("The duration " + s + " is invalid, it must be a positive, non-zero finite duration") + } + } + + private def wrapParseFailure[T](result: Either[io.circe.Error, T], expanded: => String): T = result match { + case Right(t) => t + case Left(e) => + throw new JsonMappingException(e.getMessage, e) + } + + def readValueT[T](c: Config)(implicit d: Decoder[T]): T = withContextLoader(getClass.getClassLoader) { val expanded = c.resolve.root.render(ConfigRenderOptions.concise) - try { - mapper.readValue[T](expanded) - } catch { - case e: JsonMappingException => - val m2 = try { - val margin = 50 - val len = expanded.length() - val offset = e.getLocation().getCharOffset().toInt - val (s1, o1) = if (offset > margin) { - ("..." + expanded.substring(offset - margin + 3), margin) - } else (expanded, offset) - val l1 = s1.length() - val s2 = if (l1 - o1 > margin) { - s1.substring(0, o1 + margin - 3) + "..." - } else s1 - val m1 = "\n" + s2 + "\n" + " " * o1 + "^" - if (e.getMessage().startsWith("Can not deserialize instance of java.lang.String")) - m1 + "\nA string may have been found in place of an array, somewhere in this object" else m1 - } catch { - case f:Throwable => throw new JsonMappingException("Internal dbuild exception while deserializing; please report.", e) - } - throw new JsonMappingException(e.getMessage.split("\n")(0) + m2, e.getCause) - } + wrapParseFailure(parse(expanded).flatMap(_.as[T]), expanded) } - def readValue[T](f: File)(implicit m: Manifest[T]) = readValueT[T](parseFile(f)) - def readValue[T](s: String)(implicit m: Manifest[T]) = readValueT[T](parseString(s)) - def writeValue[T](t: T)(implicit m: Manifest[T]) = - withContextLoader(getClass.getClassLoader) { mapper.writeValueAsString[T](t) } - def writeValueFormatted[T](t: T)(implicit m: Manifest[T]) = + def readValue[T](f: File)(implicit d: Decoder[T]): T = readValueT[T](parseFile(f)) + def readValue[T](s: String)(implicit d: Decoder[T]): T = readValueT[T](parseString(s)) + def writeValue[T](t: T)(implicit e: Encoder[T]): String = + withContextLoader(getClass.getClassLoader) { e(t).noSpaces } + def writeValueFormatted[T](t: T)(implicit e: Encoder[T]): String = com.typesafe.config.ConfigFactory.parseString(writeValue(t)).root.render(ConfigRenderOptions.concise.setFormatted(true)) - def readProperties(f: File) = { + def readProperties(f: File): Seq[String] = { val config = parseFile(f) // do not resolve yet! some needed vars may be in prop files which have not been parsed yet // resolve *only* the properties key, in case we are using env vars there @@ -63,24 +98,17 @@ object Utils { } catch { case e: Missing => "[]" } - try { - mapper.readValue[SeqString](rendered) - } catch { - case e:Throwable => throw new JsonMappingException("The \"properties\" section contains unexpected data.", e) + parse(rendered).flatMap(_.as[Seq[String]]) match { + case Right(v) => v + case Left(e) => throw new JsonMappingException("The \"properties\" section contains unexpected data.", e) } } - - private val mapper2 = JacksMapper // specific simplified variant to deal with reading a path from a /possible/ Artifactory response, // as well as a possible response from Flowdock - def readSomePath[T](s: String)(implicit m: Manifest[T]) = + def readSomePath[T](s: String)(implicit d: Decoder[T]): Option[T] = withContextLoader(getClass.getClassLoader) { - try { - Some(mapper2.readValue[T](s)) - } catch { - case e: JsonMappingException => None - } + parse(s).flatMap(_.as[T]).toOption } // verify whether project/space names are legal diff --git a/metadata/src/test/scala/com/typesafe/dbuild/model/DBuildConfigSpec.scala b/metadata/src/test/scala/com/typesafe/dbuild/model/DBuildConfigSpec.scala index cff9f8271..42596781b 100644 --- a/metadata/src/test/scala/com/typesafe/dbuild/model/DBuildConfigSpec.scala +++ b/metadata/src/test/scala/com/typesafe/dbuild/model/DBuildConfigSpec.scala @@ -1,9 +1,9 @@ package com.typesafe.dbuild.model -import Utils._ -import org.specs2.mutable.Specification -import com.typesafe.dbuild.model._ +import Utils.* import Utils.{writeValue,readValue} +import com.typesafe.dbuild.model.* +import org.specs2.mutable.Specification object DBuildConfigSpec extends Specification { "DBuildConfig" should { @@ -45,7 +45,7 @@ object DBuildConfigSpec extends Specification { setVersion = Some("3.9.43"), setVersionSuffix = None, extra = readValue[Option[SbtExtraConfig]]("{directory = ZOMG}") - )), None + )), None )) } } diff --git a/metadata/src/test/scala/com/typesafe/dbuild/model/RepeatableBuildSpec.scala b/metadata/src/test/scala/com/typesafe/dbuild/model/RepeatableBuildSpec.scala index 357d8a211..c81509e5f 100644 --- a/metadata/src/test/scala/com/typesafe/dbuild/model/RepeatableBuildSpec.scala +++ b/metadata/src/test/scala/com/typesafe/dbuild/model/RepeatableBuildSpec.scala @@ -1,11 +1,8 @@ package com.typesafe.dbuild.model -import org.specs2.mutable.Specification +import SeqDepsModifiersH.* import Utils.{writeValue,readValue} -import SeqDepsModifiersH._ - -import com.lambdaworks.jacks._ -import JacksOption._ +import org.specs2.mutable.Specification object RepeatableDBuildConfigSpec extends Specification { @@ -96,12 +93,12 @@ object RepeatableDBuildConfigSpec extends Specification { result must equalTo(sample) } "Make unique project build UUID" in { - val uuids: Set[String] = (sample.repeatableBuilds map (_.uuid))(collection.breakOut) + val uuids: Set[String] = sample.repeatableBuilds.map(_.uuid).toSet uuids.size must equalTo(5) } "Must make repeatable UUIDs" in { - val uuids: Set[String] = (sample.repeatableBuilds map (_.uuid))(collection.breakOut) - val uuids2: Set[String] = (sample2.repeatableBuilds map (_.uuid))(collection.breakOut) + val uuids: Set[String] = sample.repeatableBuilds.map(_.uuid).toSet + val uuids2: Set[String] = sample2.repeatableBuilds.map(_.uuid).toSet val union = uuids & uuids2 union.size must equalTo(3) } diff --git a/plugin/src/main/scala/com/typesafe/dbuild/plugin/DBuildKeys.scala b/plugin/src/main/scala/com/typesafe/dbuild/plugin/DBuildKeys.scala index c20cfae41..15396b438 100644 --- a/plugin/src/main/scala/com/typesafe/dbuild/plugin/DBuildKeys.scala +++ b/plugin/src/main/scala/com/typesafe/dbuild/plugin/DBuildKeys.scala @@ -1,12 +1,14 @@ package com.typesafe.dbuild.plugin -import sbt._ import com.typesafe.dbuild.model +import sbt.* object DBuildKeys { // TODO - make a task that generates this metadata and just call it! type ArtifactMap = Seq[model.ArtifactLocation] + @transient val extractArtifacts = TaskKey[ArtifactMap]("dbuild-extract-artifacts") // Used during the index generation + @transient val moduleInfo = TaskKey[com.typesafe.dbuild.manifest.ModuleInfo]("module-info") } \ No newline at end of file diff --git a/plugin/src/main/scala/com/typesafe/dbuild/plugin/DBuildRunner.scala b/plugin/src/main/scala/com/typesafe/dbuild/plugin/DBuildRunner.scala index 38df34cc6..f3d46a318 100644 --- a/plugin/src/main/scala/com/typesafe/dbuild/plugin/DBuildRunner.scala +++ b/plugin/src/main/scala/com/typesafe/dbuild/plugin/DBuildRunner.scala @@ -1,37 +1,36 @@ package com.typesafe.dbuild.plugin -import sbt._ +import DBuildKeys.* +import DependencyAnalysis.{ normalizedProjectNames, normalizedProjectName } +import StateHelpers.* import com.typesafe.dbuild.adapter.Adapter -import Adapter.{ProjectResolver,scalaInstance,allPaths,Load,applyCross,ScalaInstance} -import Adapter.{moduleWithName,moduleWithRevision,moduleWithCrossVersion,moduleWithExplicitArtifacts} -import Adapter.{moduleWithExtraAttributes,ivyScalaWithCheckExplicit,artifactWithClassifier} -import Adapter.{crossDisabled,crossFull,crossBinary,newIvyPaths,keyIvyScala,interProjectResolver} import Adapter.reapplySettings +import Adapter.{scalaInstance,allPaths,Load,applyCross,newIvyPaths,interProjectResolver,csrInterProjectDependencies} +import com.typesafe.dbuild.hashing import com.typesafe.dbuild.model -import com.typesafe.dbuild.support.sbt.SbtBuildConfig import com.typesafe.dbuild.model.ArtifactLocation import com.typesafe.dbuild.model.ArtifactSha +import com.typesafe.dbuild.model.BuildArtifactsInMulti +import com.typesafe.dbuild.model.BuildInput +import com.typesafe.dbuild.model.BuildSubArtifactsOut +import com.typesafe.dbuild.model.SavedConfiguration +import com.typesafe.dbuild.model.SbtPluginAttrs import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } -import StateHelpers._ -import DependencyAnalysis.{ normalizedProjectNames, normalizedProjectName } -import DBuildKeys._ -import com.typesafe.dbuild.support.NameFixer.fixName -import java.io.File +import com.typesafe.dbuild.project.build.BuildDirs.* import com.typesafe.dbuild.repo.core.LocalArtifactMissingException -import org.apache.ivy.core.module.id.ModuleRevisionId import com.typesafe.dbuild.repo.core.LocalRepoHelper -import com.typesafe.dbuild.model.BuildSubArtifactsOut -import com.typesafe.dbuild.model.SavedConfiguration -import com.typesafe.dbuild.model.BuildArtifactsInMulti -import com.typesafe.dbuild.project.build.BuildDirs._ -import com.typesafe.dbuild.model.BuildInput -import com.typesafe.dbuild.hashing +import com.typesafe.dbuild.support.NameFixer.fixName +import com.typesafe.dbuild.support.SbtUtil.{ pluginAttrs, fixAttrs } +import com.typesafe.dbuild.support.sbt.SbtBuildConfig import com.typesafe.dbuild.support.sbt.SbtRunner.{ sbtIvyCache, rewireInputFile, buildArtsFile, genArtsInputFile } import com.typesafe.dbuild.support.sbt.{ RewireInput, GenerateArtifactsInput } -import com.typesafe.dbuild.support.SbtUtil.{ pluginAttrs, fixAttrs } -import com.typesafe.dbuild.model.SbtPluginAttrs -import scala.reflect.ClassManifest +import java.io.File +import org.apache.ivy.core.module.id.ModuleRevisionId import org.apache.oro.text.regex +import sbt.internal.inc.ScalaInstance +import sbt.librarymanagement.{ Disabled, Binary, Full } +import sbt.{ *, given } +import sbtcompat.PluginCompat.{ *, given } object DBuildRunner { @@ -44,7 +43,7 @@ object DBuildRunner { // the like that are no longer used, allowing them to be really // reclaimed by GC, which in turn frees the classloaders, and // consequently also the corresponding space in PermGen. Phew! - private def purge() { + private def purge(): Unit = { System.gc() // finalizable objects that are unreachable are passed to the finalizer thread java.lang.System.runFinalization() // finalization is forced for all those objects System.gc() // those finalized objects are deallocated for good @@ -53,8 +52,8 @@ object DBuildRunner { /** Runs a series of commands across projects, aggregating results. */ private def runAggregate[Q, T](state: State, projects: Seq[String], init: Q)(merge: (Q, T) => Q)(f: (ProjectRef, State, Q) => (State, T)): (State, Q) = { val extracted = Project.extract(state) - import extracted._ - val Some(baseDirectory) = Keys.baseDirectory in ThisBuild get structure.data + import extracted.* + val Some(baseDirectory) = (ThisBuild / Keys.baseDirectory).get(structure.data) val refs = getProjectRefs(extracted) // I need a subset of refs, a sequence in the order specified by "project" val newRefs = getSortedProjects(projects, refs, baseDirectory, acceptPatterns = false) @@ -136,7 +135,7 @@ object DBuildRunner { def fixModule(arts: Seq[model.ArtifactLocation], modules: Seq[ModuleRevisionId], crossVersion: String, checkMissing: Boolean, fromSpace: String, log: Logger)(m: ModuleID): ModuleID = { def expandName(a: Artifact) = { - import a._ + import a.* classifier match { case None => fixName(name) case Some(clas) => fixName(name) + "-" + clas @@ -155,10 +154,10 @@ object DBuildRunner { // if the requested library has an explicit matching name and no classifier; // this is required by one of the test projects. So we match in that way as // well (see above m.explicitArtifacts contains...) - val m2 = moduleWithName(m, fixName(m.name) + art.crossSuffix) - val m3 = moduleWithRevision(m2, art.version) - val m4 = moduleWithCrossVersion(m3, crossDisabled) - val m5 = moduleWithExplicitArtifacts(m4, + val m2 = m.withName(fixName(m.name) + art.crossSuffix) + val m3 = m2.withRevision(art.version) + val m4 = m3.withCrossVersion(Disabled()) + val m5 = m4.withExplicitArtifacts( // More hacking: the explicit artifact did not originally contain the crossSuffix; if we leave it in place as-is, the name in the // explicit artifact takes over, and fixName(m.name)+art.crossSuffix gets ignored. Conversely, if we remove the explicit artifact // in order to make the name (with crossSuffic) match, we may lose a classifier specified in the initial explicit artifact, with the @@ -170,11 +169,11 @@ object DBuildRunner { // explicitArtifact is "compiler-interface_2.11", classifier bin, therefore "compiler-interface_2.11-bin", while // art.info.name is "compiler-interface-bin_2.11". Oops. We fix that by appending the suffix to the /classifier/ instead, // in order to make things somehow work. - artifactWithClassifier(a, Some(fixName(a.classifier.get) + art.crossSuffix)) + a.withClassifier(Some(fixName(a.classifier.get) + art.crossSuffix)) else a - }) - val newArt = moduleWithExtraAttributes(m5,fixAttrs(m.extraAttributes, art.pluginAttrs)) + }.toVector) + val newArt = m5.withExtraAttributes(fixAttrs(m.extraAttributes, art.pluginAttrs)) log.debug("Rewriting " + m + " to " + newArt + " (against: " + art + " )") newArt } getOrElse { @@ -189,7 +188,7 @@ object DBuildRunner { if (!(modules exists { i => i.getOrganisation == m.organization && fixName(i.getName) == fixName(m.name) })) { // Do we have a Scala-based library dependency that was not rewritten? We can recognize it // since there is a cross version suffix attached to the name, hence m.name != fixName(m.name) - if ((m.name != fixName(m.name) || m.crossVersion != crossDisabled)) { + if ((m.name != fixName(m.name) || m.crossVersion != Disabled())) { // If we are here, it means that this is a library dependency that is required, // that refers to an artifact that is not provided by any project in this build, // and that needs a certain Scala version (range) in order to work as intended. @@ -234,7 +233,7 @@ object DBuildRunner { def inNScopes(n: Int) = if (n == 1) "in one scope" else "in " + n + " scopes" - def fixPublishTos2(repoDir: File)(oldSettings: Seq[Setting[_]], log: Logger): Seq[Setting[_]] = { + def fixPublishTos2(repoDir: File)(oldSettings: Seq[Setting[?]], log: Logger): Seq[Setting[?]] = { val name = "deploy-to-local-repo" val mavenRepo = Some(Resolver.file(name, repoDir)(Resolver.mavenStylePatterns)) val ivyRepo = Some(Resolver.file(name, repoDir)(Resolver.ivyStylePatterns)) @@ -265,8 +264,9 @@ object DBuildRunner { val newSettings1 = { ptSettings map { s => val sc = s.key.scope - Keys.publishTo in sc := { - (Keys.publishTo in sc).value match { + sc / Keys.publishTo := Def.uncached { + val old = (sc / Keys.publishTo).value + old match { case Some(r: PatternsBasedRepository) if (!r.patterns.isMavenCompatible) => ivyRepo case _ => mavenRepo } @@ -281,14 +281,17 @@ object DBuildRunner { val newSettings = newSettings1 ++ { pmsSettings map { s => val sc = s.key.scope - Keys.publishTo in sc := { if ((Keys.publishMavenStyle in sc).value) mavenRepo else ivyRepo } + sc / Keys.publishTo := Def.uncached { + val mavenStyle = (sc / Keys.publishMavenStyle).value + if (mavenStyle) mavenRepo else ivyRepo + } } } if (newSettings.isEmpty) { log.info("No publishTo or publishMavenStyle settings found; adding a default Maven publishTo.") - Seq(Keys.publishTo in ThisBuild := mavenRepo, - Keys.publishMavenStyle in ThisBuild := true) + Seq(ThisBuild / Keys.publishTo := Def.uncached(mavenRepo), + ThisBuild / Keys.publishMavenStyle := true) } else newSettings } @@ -307,26 +310,26 @@ object DBuildRunner { // // Collect the last settings in each scope that (re)define a given key - private def lastSettingsByScope(oldSettings: Seq[Setting[_]], theKey: Scoped): Seq[Setting[_]] = { + private def lastSettingsByScope(oldSettings: Seq[Setting[?]], theKey: Scoped): Seq[Setting[?]] = { val key = theKey.key oldSettings.filter(_.key.key == key).groupBy(_.key.scope).map(_._2.last).toSeq } // applies a generic transformation from Setting[K] (the old one) to another Setting[K] (the new one) - def fixGenericTransform2[K](k: Scoped)(f: Setting[K] => Setting[K])(msg: String)(oldSettings: Seq[Setting[_]], log: Logger) = { + def fixGenericTransform2[K](k: Scoped)(f: Setting[K] => Setting[K])(msg: String)(oldSettings: Seq[Setting[?]], log: Logger) = { val lastSettings = lastSettingsByScope(oldSettings, k) if (lastSettings.nonEmpty) log.info(msg + " " + inNScopes(lastSettings.length)) lastSettings.asInstanceOf[Seq[Setting[K]]] map f } // as above, but assumes the transformation is a simple Project.update (aka: ~= ) - def fixGenericK2[K](k: Scoped, f: K => K) = fixGenericTransform2(k) { s: Setting[K] => Def.update(s.key)(f) } _ + def fixGenericK2[K](k: Scoped, f: K => K) = fixGenericTransform2(k) { (s: Setting[K]) => Def.update(s.key)(f) } _ // Separate cases for settings and tasks (to keep the type inferencer happy) def fixGeneric2[K](k: SettingKey[K], m: String)(f: K => K) = fixGenericK2(k, f)(m) def fixGeneric2[K](k: TaskKey[K], m: String)(f: Task[K] => Task[K]) = fixGenericK2(k, f)(m) - type Fixer = (Seq[Setting[_]], Logger) => Seq[Setting[_]] + type Fixer = (Seq[Setting[?]], Logger) => Seq[Setting[?]] // There are two parts in dealing with cross-versions. // 1) Keys.crossVersion will affect how the projects are published @@ -349,20 +352,20 @@ object DBuildRunner { // (this is for testing only). def fixCrossVersions2(crossVersion: String) = { val scalaBinaryFull = - fixGenericTransform2(Keys.scalaBinaryVersion) { s: Setting[String] => + fixGenericTransform2(Keys.scalaBinaryVersion) { (s: Setting[String]) => val sc = s.key.scope - Keys.scalaBinaryVersion in sc := (Keys.scalaVersion in sc).value + sc / Keys.scalaBinaryVersion := (sc / Keys.scalaVersion).value }("Setting Scala binary version to full") _ crossVersion match { case "binaryFull" => scalaBinaryFull - case "standard" => { (_: Seq[Setting[_]], _: Logger) => Seq.empty } - case "disabled" => { (s: Seq[Setting[_]], l: Logger) => - fixGeneric2(Keys.crossVersion, "Disabling cross version") { _ => crossDisabled }(s, l) ++ + case "standard" => { (_: Seq[Setting[?]], _: Logger) => Seq.empty } + case "disabled" => { (s: Seq[Setting[?]], l: Logger) => + fixGeneric2(Keys.crossVersion, "Disabling cross version") { _ => Disabled() }(s, l) ++ scalaBinaryFull(s, l) } - case "full" => { (s: Seq[Setting[_]], l: Logger) => - fixGeneric2(Keys.crossVersion, "Setting cross version to full") { _ => crossFull }(s, l) ++ + case "full" => { (s: Seq[Setting[?]], l: Logger) => + fixGeneric2(Keys.crossVersion, "Setting cross version to full") { _ => Full() }(s, l) ++ scalaBinaryFull(s, l) } case _ => sys.error("Unrecognized option \"" + crossVersion + "\" in cross-version") @@ -373,10 +376,10 @@ object DBuildRunner { // This is necessary in case some required inter-project dependencies have been explicitly excluded. def fixDependencies2(locs: Seq[model.ArtifactLocation], modules: Seq[ModuleRevisionId], crossVersion: String, checkMissing: Boolean, fromSpace: String, log: Logger) = - fixGenericTransform2(Keys.allDependencies) { r: Setting[Task[Seq[sbt.ModuleID]]] => + fixGenericTransform2(Keys.allDependencies) { (r: Setting[Task[Seq[sbt.ModuleID]]]) => val sc = r.key.scope - Keys.allDependencies in sc := { - val old = (Keys.allDependencies in sc).value + sc / Keys.allDependencies := Def.uncached { + val old = (sc / Keys.allDependencies).value old map fixModule(locs, modules, crossVersion, checkMissing, fromSpace, log) } }("Updating dependencies") _ @@ -385,11 +388,11 @@ object DBuildRunner { def fixOverrides2(locs: Seq[model.ArtifactLocation], modules: Seq[ModuleRevisionId], crossVersion: String, checkMissing: Boolean, rewriteOverrides: Boolean, fromSpace: String, log: Logger) = if (!rewriteOverrides) - { (settings:Seq[sbt.Setting[_]], _:sbt.Logger) => Seq.empty } - else fixGenericTransform2(Keys.dependencyOverrides) { r: Setting[_] => + { (settings:Seq[sbt.Setting[?]], _:sbt.Logger) => Seq.empty } + else fixGenericTransform2(Keys.dependencyOverrides) { (r: Setting[?]) => val sc = r.key.scope - Keys.dependencyOverrides in sc := { - val old = (Keys.dependencyOverrides in sc).value + sc / Keys.dependencyOverrides := { + val old = (sc / Keys.dependencyOverrides).value old map fixModule(locs, modules, crossVersion, checkMissing, fromSpace, log) } }("Updating overrides") _ @@ -424,7 +427,7 @@ object DBuildRunner { // a shortened version). That generates tons of warnings; in order to disable that, we set // IvyScala.checkExplicit to false def fixScalaBinaryCheck2 = - fixGeneric2(keyIvyScala, "Disabling Scala binary checking") { _ map { i => ivyScalaWithCheckExplicit(i, false) } } + fixGeneric2(sbt.Keys.scalaModuleInfo, "Disabling Scala binary checking") { _ map { i => i.withCheckExplicit(false) } } // We need to disable the inter-project resolver entirely. Otherwise, sbt will try to build all // of the dependent subprojects each time one of the subprojects is built, including some that @@ -443,10 +446,10 @@ object DBuildRunner { // of the selected subprojects. Might be more suitable for setupcmd; in this case, // local-publish-repo should not be added to the list of resolvers. def fixInterProjectResolver2bis(modules: Seq[ModuleRevisionId]) = - fixGenericTransform2(Keys.projectResolver) { r: Setting[Task[Resolver]] => + fixGenericTransform2(Keys.projectResolver) { (r: Setting[Task[Resolver]]) => val sc = r.key.scope - Keys.projectResolver in sc := { - val m = (Keys.projectDescriptors in sc).value + sc / Keys.projectResolver := Def.uncached { + val m = (sc / Adapter.projectDescriptorsKey).value val k = m.filter { case (a, _) => modules exists { b => b.getOrganisation == a.getOrganisation && fixName(b.getName) == fixName(a.getName) } } @@ -454,16 +457,29 @@ object DBuildRunner { } }("Patching the inter-project resolver") _ + // Coursier-native counterpart to fixInterProjectResolver2bis. + def fixCsrInterProjectDependencies2bis(modules: Seq[ModuleRevisionId]) = + fixGenericTransform2(Keys.csrInterProjectDependencies) { (r: Setting[Task[Seq[lmcoursier.definitions.Project]]]) => + val sc = r.key.scope + sc / Keys.csrInterProjectDependencies := Def.uncached { + val m = (sc / Adapter.projectDescriptorsKey).value + val k = m.filter { + case (a, _) => modules exists { b => b.getOrganisation == a.getOrganisation && fixName(b.getName) == fixName(a.getName) } + } + csrInterProjectDependencies(k) + } + }("Patching the coursier inter-project dependencies") _ + // Fix the ivy home location, so that each level has its own separate cache. // It is necessary to have one per level, since different levels may resolve // from different spaces, and different spaces may contain artifacts that have // same org, name, version, but that are actually different. Artifacts that // belong to different spaces must never come into contact with each other. def fixIvyPaths2(log: Logger) = - fixGenericTransform2(Keys.baseDirectory) { r: Setting[IvyPaths] => + fixGenericTransform2(Keys.baseDirectory) { (r: Setting[IvyPaths]) => val sc = r.key.scope - Keys.ivyPaths in sc := { - val d = (Keys.baseDirectory in sc).value + sc / Keys.ivyPaths := { + val d = (sc / Keys.baseDirectory).value newIvyPaths(d, Some(sbtIvyCache(d))) } }("Patching Ivy paths") _ @@ -471,7 +487,7 @@ object DBuildRunner { // extract the ModuleRevisionIds of all the subprojects of this dbuild project (as calculated from exclusions, dependencies, etc). def getModuleRevisionIds(state: State, projects: Seq[String], log: Logger) = runAggregate[Seq[ModuleRevisionId], ModuleRevisionId](state, projects, Seq.empty) { _ :+ _ } { - (proj, state, _) => (state, Project.extract(state).runTask(Keys.ivyModule in proj, state)._2.dependencyMapping(log)._1) + (proj, state, _) => (state, Project.extract(state).runTask(proj / Keys.ivyModule, state)._2.dependencyMapping(log)._1) }._2 // In order to convince sbt to use the scala instance we need, we just generate a fictitious @@ -480,7 +496,7 @@ object DBuildRunner { // Java 6 has no symlinks facility, therefore the files need to be copied. // // repoDir is the local-repo, which should already contain the re-materialized files - def fixScalaVersion2(dbuildDir: File, repoDir: File, locs: Seq[model.ArtifactLocation])(oldSettings: Seq[Setting[_]], log: Logger) = { + def fixScalaVersion2(dbuildDir: File, repoDir: File, locs: Seq[model.ArtifactLocation])(oldSettings: Seq[Setting[?]], log: Logger) = { customScalaVersion(locs).toSeq flatMap { ver => log.info("Preparing Scala binaries: scala-library version " + ver) val scalaArts = locs.filter(scalaOrgs contains _.info.organization) @@ -496,10 +512,10 @@ object DBuildRunner { def libraryJar(scalaHome: File) = scalaJar(scalaHome, "scala-library.jar") // fixGeneric2(Keys.scalaVersion, "Setting Scala version to: " + ver) { _ => ver }(oldSettings, log) ++ - fixGenericTransform2(Keys.scalaInstance) { s: Setting[Task[ScalaInstance]] => + fixGenericTransform2(Keys.scalaInstance) { (s: Setting[Task[ScalaInstance]]) => val sc = s.key.scope - Keys.scalaInstance in sc := { - val app = (Keys.appConfiguration in sc).value + sc / Keys.scalaInstance := Def.uncached { + val app = (sc / Keys.appConfiguration).value val launcher = app.provider.scalaProvider.launcher scalaInstance(libraryJar(scalaHome), compilerJar(scalaHome), launcher, allJars(scalaHome): _*) } @@ -553,7 +569,7 @@ object DBuildRunner { } } - def fixPGPs2(oldSettings: Seq[Setting[_]], log: Logger) = + def fixPGPs2(oldSettings: Seq[Setting[?]], log: Logger) = fixGeneric2(Keys.skip, "Disabling PGP signing") { old => old map (_ => true) }(oldSettings.filter { _.key.scope.task.toOption match { case Some(scope) if scope.label.toString == "pgp-signer" => true @@ -563,10 +579,10 @@ object DBuildRunner { def printPR(state: State): Unit = { val extracted = Project.extract(state) - import extracted._ + import extracted.* val refs = getProjectRefs(extracted) refs foreach { ref => - val resolver = extracted.runTask(Keys.projectResolver in ref, state)._2 + val resolver = extracted.runTask(ref / Keys.projectResolver, state)._2 println("\tprojectResolver in %s: (%s)" format (ref.project, resolver)) } } @@ -574,11 +590,11 @@ object DBuildRunner { def printResolvers(state: State): Unit = { println("Using resolvers:") val extracted = Project.extract(state) - import extracted._ + import extracted.* val refs = getProjectRefs(extracted) for { ref <- refs - (_, resolvers) = extracted.runTask(Keys.fullResolvers in ref, state) + (_, resolvers) = extracted.runTask(ref / Keys.fullResolvers, state) r <- resolvers } println("\t(%s) - %s" format (r.name, r.toString)) } @@ -599,7 +615,7 @@ object DBuildRunner { // printPR(state2) val refs = getProjectRefs(Project.extract(state2)) - val Some(baseDirectory) = Keys.baseDirectory in ThisBuild get Project.extract(state2).structure.data + val Some(baseDirectory) = (ThisBuild / Keys.baseDirectory).get(Project.extract(state2).structure.data) println(config.info.subproj.head.mkString("These subprojects will be built: ", ", ", "")) val buildAggregate = runAggregate[(Seq[File], Seq[BuildSubArtifactsOut]), (Seq[File], BuildSubArtifactsOut)](state2, config.info.subproj.head, (Seq.empty, Seq.empty)) { @@ -609,7 +625,7 @@ object DBuildRunner { def buildTestPublish(ref: ProjectRef, state6: State, previous: (Seq[File], Seq[BuildSubArtifactsOut])): (State, (Seq[File], BuildSubArtifactsOut)) = { println("----------------------") println("Processing subproject: " + normalizedProjectName(ref, baseDirectory)) - val (_, libDeps) = Project.extract(state6).runTask(Keys.allDependencies in ref, state6) + val (_, libDeps) = Project.extract(state6).runTask(ref / Keys.allDependencies, state6) println("All Dependencies for subproject " + normalizedProjectName(ref, baseDirectory) + ":") libDeps foreach { m => println(" " + m) } // val (_,pRes) = Project.extract(state6).runTask(Keys.projectResolver in ref, state) @@ -619,13 +635,13 @@ object DBuildRunner { def doTask[T](old: State, task: TaskKey[T], msg: String = "") = { if (msg.nonEmpty) println(msg + ": " + normalizedProjectName(ref, baseDirectory)) - Project.extract(old).runTask(task in ref, old) + Project.extract(old).runTask(ref / task, old) } def doInputTask[T](old: State, task: InputKey[T], input: String, msg: String = "") = { if (msg.nonEmpty) println(msg + ": " + normalizedProjectName(ref, baseDirectory)) - Project.extract(old).runInputTask(task in ref, input, old) + Project.extract(old).runInputTask(ref / task, input, old) } val (state7, artifacts) = doTask(state6, extractArtifacts, "Running build") @@ -642,41 +658,38 @@ object DBuildRunner { case _ => sys.error("Malformed task description: \"" + taskAndConfig + "\"") } val conf = new ConfigKey(configuration) - val scope = Scope.ThisScope.in(ref, conf) + val scope = Adapter.rescopeForTest(ref, conf) val structure = Project.extract(state7).structure val index = structure.index.keyIndex val sel = structure.index.keyMap.get(task) def keyFromTask[T](a: AttributeKey[Task[T]]) = TaskKey[T](a) def keyFromInputTask[T](a: AttributeKey[InputTask[T]]) = InputKey[T](a) - // sel is now an Option[sbt.AttributeKey[_]]. Since we don't know the - // inner type parameter, we cannot really build a matching TaskKey[_]. + // sel is now an Option[sbt.AttributeKey[?]]. Since we don't know the + // inner type parameter, we cannot really build a matching TaskKey[?]. // However, we only need to call runTask() on it, and sbt.Extracted.runTask() - // doesn't need any manifest (there is one directly inside the AttributeKey). + // doesn't need any tag (there is one directly inside the AttributeKey). // So, we should be safe by crudely casting. - val taskManifest = ClassManifest.fromClass(classOf[Task[_]]).erasure - val inputTaskManifest = ClassManifest.fromClass(classOf[InputTask[_]]).erasure sel match { case None => if (config.testParams.skipMissingTests) { println("Warning: the test task key \"" + task + "\" is not known in " + normalizedProjectName(ref, baseDirectory) + ", so skipping.") old } else sys.error("You asked dbuild to test using the task \"" + task + "\", but the task key is unknown in project \"" + normalizedProjectName(ref, baseDirectory) + "\".") - case Some(key) if (structure.data.get(scope, key).isEmpty) => + case Some(key) if !Adapter.isKeyDefined(structure.data, scope, key) => if (config.testParams.skipMissingTests) { println("Warning: the test task key \"" + task + "\" is known but it is not defined in " + normalizedProjectName(ref, baseDirectory) + ", so skipping.") old } else sys.error("You asked dbuild to test using the task \"" + task + "\". The task key exists, but it is undefined in project \"" + normalizedProjectName(ref, baseDirectory) + "\".") case Some(key) => // does this AttributeKey refer to a Task ? - if (key.manifest.erasure == taskManifest) { // select AttributeKey[Task[whatever]] - doTask(old, keyFromTask(key.asInstanceOf[AttributeKey[Task[Any]]]) in - scope, "Running \"" + task + "\" in")._1 - } else if (key.manifest.erasure == inputTaskManifest) { - doInputTask(old, keyFromInputTask(key.asInstanceOf[AttributeKey[InputTask[Any]]]) in - scope, input, "Running \"" + task + "\" in")._1 - } else { - sys.error("Not a task: " + task + ", (found: " + key.manifest + ")") + Adapter.classifyKey(key) match { + case Adapter.TaskKeyKind => + doTask(old, scope / keyFromTask(key.asInstanceOf[AttributeKey[Task[Any]]]), "Running \"" + task + "\" in")._1 + case Adapter.InputTaskKeyKind => + doInputTask(old, scope / keyFromInputTask(key.asInstanceOf[AttributeKey[InputTask[Any]]]), input, "Running \"" + task + "\" in")._1 + case Adapter.OtherKeyKind => + sys.error("Not a task: " + task + ", (found: " + key + ")") } } } @@ -693,7 +706,7 @@ object DBuildRunner { // current set of files in the repository against the files we had previously val previousFiles = previous._1 val localRepo = config.info.outRepo.getAbsoluteFile - val currentFiles = (allPaths(localRepo)).get. + val currentFiles = (allPaths(localRepo)).get(). filterNot(file => file.isDirectory || file.getName == "maven-metadata-local.xml") val newFilesShas = currentFiles.diff(previousFiles).map { LocalRepoHelper.makeArtifactSha(_, localRepo) } @@ -712,7 +725,7 @@ object DBuildRunner { } def loadBuildArtifacts(localRepos: Seq /*Levels*/ [File], builduuid: String, thisProject: String, log: Logger, debug: Boolean) = { - import com.typesafe.dbuild.repo.core._ + import com.typesafe.dbuild.repo.core.* val cache = Repository.default val project = findRepeatableProjectBuild(builduuid, thisProject, log) log.info("Retrieving dependencies for " + project.uuid + " " + project.config.name) @@ -723,7 +736,7 @@ object DBuildRunner { } def findRepeatableProjectBuild(builduuid: String, thisProject: String, log: Logger) = { - import com.typesafe.dbuild.repo.core._ + import com.typesafe.dbuild.repo.core.* log.info("Finding information for project " + thisProject + " in build " + builduuid) val cache = Repository.default val projects = (for { @@ -737,7 +750,7 @@ object DBuildRunner { } private def prepareCompileSettings(log: ConsoleLogger, modules: Seq[ModuleRevisionId], dbuildDir: File, - repoDir: File, arts: Seq[ArtifactLocation], oldSettings: Seq[Setting[_]], crossVersion: String, + repoDir: File, arts: Seq[ArtifactLocation], oldSettings: Seq[Setting[?]], crossVersion: String, checkMissing: Boolean, rewriteOverrides: Boolean, fromSpace: String) = { Seq[Fixer]( fixResolvers2(repoDir, log), @@ -745,20 +758,21 @@ object DBuildRunner { fixOverrides2(arts, modules, crossVersion, checkMissing, rewriteOverrides, fromSpace, log), fixScalaVersion2(dbuildDir, repoDir, arts), fixInterProjectResolver2bis(modules), + fixCsrInterProjectDependencies2bis(modules), fixCrossVersions2(crossVersion), fixIvyPaths2(log), fixScalaBinaryCheck2) flatMap { _(oldSettings, log) } } - private def preparePublishSettings(in: BuildInput, log: ConsoleLogger, oldSettings: Seq[Setting[_]]) = + private def preparePublishSettings(in: BuildInput, log: ConsoleLogger, oldSettings: Seq[Setting[?]]) = Seq[Fixer]( fixStandardPublish2, fixPublishTos2(in.outRepo.getAbsoluteFile), fixPGPs2, fixVersions2(in)) flatMap { _(oldSettings, log) } - def newState(state: State, extracted: Extracted, update: Seq[Setting[_]] => Seq[Setting[_]], log: Logger) = { - import extracted._ + def newState(state: State, extracted: Extracted, update: Seq[Setting[?]] => Seq[Setting[?]], log: Logger) = { + import extracted.{ given, * } val oldSettings = session.mergeSettings val newSettings = update(oldSettings) @@ -780,10 +794,10 @@ object DBuildRunner { /** called by onLoad() during building */ def rewire(state: State, previousOnLoad: State => State, fixPublishSettings: Boolean = false): State = { - import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames._ + import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames.* val extracted = Project.extract(state) - val Some(baseDirectory) = sbt.Keys.baseDirectory in ThisBuild get extracted.structure.data + val Some(baseDirectory) = (ThisBuild / sbt.Keys.baseDirectory).get(extracted.structure.data) val inputFile = rewireInputFile(baseDirectory) val rewireInfo = readValue[RewireInput](inputFile) val log = sbt.ConsoleLogger() @@ -793,16 +807,16 @@ object DBuildRunner { val modules = getModuleRevisionIds(state, rewireInfo.subproj, log) - def restore(oldSettings: Seq[Setting[_]]) = restorePreviousOnLoad(previousOnLoad)(oldSettings, log) + def restore(oldSettings: Seq[Setting[?]]) = restorePreviousOnLoad(previousOnLoad)(oldSettings, log) - def publishSettings(oldSettings: Seq[Setting[_]]) = if (fixPublishSettings) { - val Some(baseDirectory) = sbt.Keys.baseDirectory in ThisBuild get extracted.structure.data + def publishSettings(oldSettings: Seq[Setting[?]]) = if (fixPublishSettings) { + val Some(baseDirectory) = (ThisBuild / sbt.Keys.baseDirectory).get(extracted.structure.data) val inputFile = genArtsInputFile(baseDirectory) val generateArtifactsInfo = readValue[GenerateArtifactsInput](inputFile) preparePublishSettings(generateArtifactsInfo.info, log, oldSettings) } else Seq.empty - def newSettings(oldSettings: Seq[Setting[_]]) = + def newSettings(oldSettings: Seq[Setting[?]]) = prepareCompileSettings(log, modules, dbuildDir, rewireInfo.in.localRepo, rewireInfo.in.artifacts, oldSettings, rewireInfo.crossVersion, rewireInfo.checkMissing, rewireInfo.rewriteOverrides, rewireInfo.in.fromSpace) ++ publishSettings(oldSettings) ++ restore(oldSettings) @@ -822,10 +836,10 @@ object DBuildRunner { * via the dbuild-build command. */ def generateArtifacts(state: State): State = { - import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames._ + import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames.* val extracted = Project.extract(state) - val Some(baseDirectory) = sbt.Keys.baseDirectory in ThisBuild get extracted.structure.data + val Some(baseDirectory) = (ThisBuild / sbt.Keys.baseDirectory).get(extracted.structure.data) val inputFile = genArtsInputFile(baseDirectory) val generateArtifactsInfo = readValue[GenerateArtifactsInput](inputFile) @@ -843,10 +857,10 @@ object DBuildRunner { private def buildIt = Command.command("dbuild-build")(generateArtifacts) /** Settings you can add your build to print dependencies. */ - def buildSettings: Seq[Setting[_]] = Seq(Keys.commands += buildIt) + def buildSettings: Seq[Setting[?]] = Seq(Keys.commands += buildIt) def extractArtifactLocations(org: String, version: String, artifacts: Map[Artifact, File], skipInPublish: Boolean, - cross: Adapter.CrossVersion, sv: String, sbv: String, sbtbv: String, isSbtPlugin: Boolean): Seq[model.ArtifactLocation] = + cross: sbt.librarymanagement.CrossVersion, sv: String, sbv: String, sbtbv: String, isSbtPlugin: Boolean): Seq[model.ArtifactLocation] = if (skipInPublish) Seq.empty else { val crossSuffix = applyCross("", CrossVersion(cross, sv, sbv)) for { @@ -860,12 +874,12 @@ object DBuildRunner { def generateModuleInfo(organization: String, name: String, version: String, scalaVersion: String, scalaBinaryVersion: String, sbtVersion: String, sbtBinaryVersion: String, isSbtPlugin: Boolean, crossVersion: sbt.CrossVersion): com.typesafe.dbuild.manifest.ModuleInfo = { - import com.typesafe.dbuild.manifest._ + import com.typesafe.dbuild.manifest.* // according to the specificatin of CrossBuildProperties: val someScala: Option[String] = crossVersion match { - case _: crossDisabled => None - case x: crossBinary => Some(scalaBinaryVersion) - case x: crossFull => Some(scalaVersion) + case _: Disabled => None + case x: Binary => Some(scalaBinaryVersion) + case x: Full => Some(scalaVersion) case _ => sys.error("Internal error: unknown crossVersion in generateModuleInfo(). Please report.") } val cbp = if (isSbtPlugin) { @@ -876,9 +890,10 @@ object DBuildRunner { ModuleInfo(organization, name, version, cbp) } - def projectSettings: Seq[Setting[_]] = Seq( + def projectSettings: Seq[Setting[?]] = Seq( + Adapter.packagedArtifactsSetting, extractArtifacts := extractArtifactLocations(Keys.organization.value, Keys.version.value, - (Keys.packagedArtifacts in Compile).value, (Keys.skip in Keys.publish).value, Keys.crossVersion.value, + Adapter.packagedArtifactsAsFiles.value, (Keys.publish / Keys.skip).value, Keys.crossVersion.value, Keys.scalaVersion.value, Keys.scalaBinaryVersion.value, Keys.sbtBinaryVersion.value, Keys.sbtPlugin.value), moduleInfo := generateModuleInfo(Keys.organization.value, Keys.moduleName.value, Keys.version.value, Keys.scalaVersion.value, Keys.scalaBinaryVersion.value, Keys.sbtVersion.value, Keys.sbtBinaryVersion.value, Keys.sbtPlugin.value, Keys.crossVersion.value) diff --git a/plugin/src/main/scala/com/typesafe/dbuild/plugin/DependencyAnalysis.scala b/plugin/src/main/scala/com/typesafe/dbuild/plugin/DependencyAnalysis.scala index f73ab6ff9..925117f31 100644 --- a/plugin/src/main/scala/com/typesafe/dbuild/plugin/DependencyAnalysis.scala +++ b/plugin/src/main/scala/com/typesafe/dbuild/plugin/DependencyAnalysis.scala @@ -1,16 +1,16 @@ package com.typesafe.dbuild.plugin -import sbt._ +import DBuildRunner.getSortedProjects import com.typesafe.dbuild.adapter.Adapter -import Adapter.syntaxio._ import Adapter.defaultID import com.typesafe.dbuild.model -import com.typesafe.dbuild.plugin.StateHelpers._ -import com.typesafe.dbuild.support.sbt.ExtractionInput -import com.typesafe.dbuild.support.NameFixer.fixName -import DBuildRunner.getSortedProjects import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } +import com.typesafe.dbuild.plugin.StateHelpers.* +import com.typesafe.dbuild.support.NameFixer.fixName +import com.typesafe.dbuild.support.sbt.ExtractionInput import org.apache.commons.io.FileUtils.writeStringToFile +import sbt.io.syntax.* +import sbt.{ *, given } object DependencyAnalysis { // TODO - make a task that generates this metadata and just call it! @@ -18,12 +18,12 @@ object DependencyAnalysis { /** Pulls the name/organization/version for each project in the build. */ private def getProjectInfos(extracted: Extracted, state: State, refs: Iterable[ProjectRef]): Seq[model.Project] = (Vector[model.Project]() /: refs) { (dependencies, ref) => - val name = fixName(extracted.get(Keys.name in ref)) - val organization = extracted.get(Keys.organization in ref) + val name = fixName(extracted.get(ref / Keys.name)) + val organization = extracted.get(ref / Keys.organization) // Project dependencies (TODO - Custom task for this...) - val (_, pdeps) = extracted.runTask(Keys.projectDependencies in ref, state) - val ldeps = extracted.get(Keys.libraryDependencies in ref) + val (_, pdeps) = extracted.runTask(ref / Keys.projectDependencies, state) + val ldeps = extracted.get(ref / Keys.libraryDependencies) def artifactsNoEmpty(name: String, arts: Seq[Artifact]) = if (!arts.isEmpty) arts else Seq(Artifact(name)) @@ -36,9 +36,9 @@ object DependencyAnalysis { model.ProjectRef("scala-compiler", "org.scala-lang", "jar", None) // Project Artifacts - val skipPublish = extracted.runTask(Keys.skip in (ref, Keys.publish), state)._2 + val skipPublish = extracted.runTask(ref / Keys.publish / Keys.skip, state)._2 val artifacts = if (skipPublish) Seq[model.ProjectRef]() else for { - a <- extracted get (Keys.artifacts in ref) + a <- extracted.get(ref / Keys.artifacts) } yield model.ProjectRef(fixName(a.name), organization, a.extension, a.classifier) // Append ourselves to the list of projects... @@ -79,8 +79,8 @@ object DependencyAnalysis { def normalizedProjectNames(r: Seq[ProjectRef], baseDirectory: File) = r map { p => normalizedProjectName(p, baseDirectory) } /** Do we need to use a specific Scala version during extraction? If so, set it now. */ - def fixExtractionScalaVersion2(opt: Option[String]): (Seq[Setting[_]], sbt.Logger) => Seq[Setting[_]] = opt match { - case None => (a: Seq[Setting[_]], _) => a + def fixExtractionScalaVersion2(opt: Option[String]): (Seq[Setting[?]], sbt.Logger) => Seq[Setting[?]] = opt match { + case None => (a: Seq[Setting[?]], _) => a case Some(v) => DBuildRunner.fixGeneric2(Keys.scalaVersion, "Setting extraction Scala version to " + v) { _ => v } } @@ -90,9 +90,9 @@ object DependencyAnalysis { val ExtractionInput(projects, excludedProjects, extractionScalaVersion, debug) = extractionInput val extracted = Project.extract(state) - import extracted._ + import extracted.* - val Some(baseDirectory) = Keys.baseDirectory in ThisBuild get structure.data + val Some(baseDirectory) = (ThisBuild / Keys.baseDirectory).get(structure.data) log.debug("Extracting dependencies: " + baseDirectory.getCanonicalPath) def normalizedProjectNames(r: Seq[ProjectRef]) = DependencyAnalysis.normalizedProjectNames(r, baseDirectory) @@ -126,7 +126,7 @@ object DependencyAnalysis { // this will be the list of ProjectRefs that will actually be built, in the right sequence val refs = { - import com.typesafe.dbuild.graph._ + import com.typesafe.dbuild.graph.* // we need to linearize the list of subprojects. If this is not done, // when we try to build one of the (sbt) subprojects, multiple ones can be // built at once, which prevents us from finding easily which files are @@ -144,8 +144,8 @@ object DependencyAnalysis { class SubProjGraph(projs: Seq[ProjectRef], directDeps: Map[ProjectRef, Set[ProjectRef]], directAggregates: Map[ProjectRef, Set[ProjectRef]]) extends Graph[ProjectRef, DepType] { val nodeMap: Map[ProjectRef, Node[ProjectRef]] = (projs map { p => (p, SimpleNode(p)) }).toMap - private def wrapEdges(kind: DepType, edges: Map[ProjectRef, Set[ProjectRef]]) = edges map { - case (from, to) => (nodeMap(from), to map nodeMap map { SimpleEdge(nodeMap(from), _, kind) } toSeq) + private def wrapEdges(kind: DepType, edges: Map[ProjectRef, Set[ProjectRef]]): Map[Node[ProjectRef], Seq[Edge[ProjectRef, DepType]]] = edges map { + case (from, to) => (nodeMap(from), (to map nodeMap map { SimpleEdge(nodeMap(from), _, kind) }).toSeq) } val nodes: Set[Node[ProjectRef]] = nodeMap.values.toSet private val edgeMap: Map[Node[ProjectRef], Seq[Edge[ProjectRef, DepType]]] = { @@ -171,16 +171,16 @@ object DependencyAnalysis { // extracted.currentUnit.defined; we consequently scan extracted.structure.allProjects // instead (which in theory should contain the same information as allRefs, // aka extracted.structure.allProjectRefs). - val allProjDeps = (extracted.structure.allProjects map { p => + val allProjDeps: Map[ProjectRef, Set[ProjectRef]] = (extracted.structure.allProjects map { p => (allProjRefsMap(p.id), (extracted.currentUnit.defined.get(p.id) map { rp => - rp.dependencies map { _.project } toSet + (rp.dependencies map { _.project }).toSet }) getOrElse Set[ProjectRef]()) }).toMap // The same, for the "aggregate" relationship (only direct ones, not the transitive set) - val allProjAggregates = extracted.structure.allProjects map { p => + val allProjAggregates: Map[ProjectRef, Set[ProjectRef]] = (extracted.structure.allProjects map { p => (allProjRefsMap(p.id), p.aggregate.toSet) - } toMap + }).toMap // some debugging won't hurt log.info("Dependencies among subprojects:") @@ -253,7 +253,7 @@ object DependencyAnalysis { val deps = getProjectInfos(extracted, state, refs) // TODO: why only the root version? We might as well grab that of each subproject - val Some(version) = Keys.version in currentRef get structure.data + val Some(version) = (currentRef / Keys.version).get(structure.data) // return just this version string now; we will append to it more stuff prior to building val meta = model.ProjMeta(version, deps, normalizedProjectNames(refs)) // return the new list of subprojects as well! @@ -264,11 +264,11 @@ object DependencyAnalysis { /** called by onLoad() during extraction */ def printCmd(state: State, previousOnLoad: State => State): State = { - import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames._ + import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames.* val extracted = Project.extract(state) - val Some(baseDirectory) = sbt.Keys.baseDirectory in ThisBuild get extracted.structure.data - import Adapter.syntaxio._ + val Some(baseDirectory) = (ThisBuild / sbt.Keys.baseDirectory).get(extracted.structure.data) + import sbt.io.syntax.* val dbuildDir = baseDirectory / dbuildSbtDirName val resultFile = dbuildDir / extractionOutputFileName val inputFile = dbuildDir / extractionInputFileName @@ -281,8 +281,8 @@ object DependencyAnalysis { val log = sbt.ConsoleLogger() if (extractionInput.debug) log.setLevel(Level.Debug) - def prepareExtractionSettings(oldSettings: Seq[Setting[_]]) = { - Seq[(Seq[Setting[_]], Logger) => Seq[Setting[_]]]( + def prepareExtractionSettings(oldSettings: Seq[Setting[?]]) = { + Seq[(Seq[Setting[?]], Logger) => Seq[Setting[?]]]( fixExtractionScalaVersion2(extractionInput.extractionScalaVersion), DBuildRunner.restorePreviousOnLoad(previousOnLoad)) flatMap { _(oldSettings, log) } } diff --git a/plugin/src/main/scala/com/typesafe/dbuild/plugin/PluginFixins.scala b/plugin/src/main/scala/com/typesafe/dbuild/plugin/PluginFixins.scala index 9e67bb0aa..faa9ba137 100644 --- a/plugin/src/main/scala/com/typesafe/dbuild/plugin/PluginFixins.scala +++ b/plugin/src/main/scala/com/typesafe/dbuild/plugin/PluginFixins.scala @@ -1,8 +1,7 @@ package com.typesafe.dbuild.plugin -import sbt._ import com.typesafe.dbuild.support.NameFixer -import com.typesafe.dbuild.adapter.Adapter.moduleWithRevision +import sbt.* object PluginFixins { case class PluginId(group: String, name: String) @@ -14,12 +13,12 @@ object PluginFixins { def fixPlugins(state: State): State = { val extracted = Project.extract(state) - import extracted._ + import extracted.* state } - def fixLibraryDependencies(s: Setting[_]): Setting[_] = + def fixLibraryDependencies(s: Setting[?]): Setting[?] = s.asInstanceOf[Setting[Seq[ModuleID]]] mapInit { (_, old) => old map fixPlugin } @@ -28,7 +27,7 @@ object PluginFixins { def fixPlugin(m: ModuleID): ModuleID = { val id = PluginId(m.organization, NameFixer.fixName(m.name)) val optVersion = pluginVersions get id - optVersion map (v => moduleWithRevision(m, v)) getOrElse m + optVersion map (v => m.withRevision(v)) getOrElse m } } diff --git a/proj/src/main/scala/com/typesafe/dbuild/project/build/BuildDirs.scala b/proj/src/main/scala/com/typesafe/dbuild/project/build/BuildDirs.scala index 6baa91240..87c63f5f9 100644 --- a/proj/src/main/scala/com/typesafe/dbuild/project/build/BuildDirs.scala +++ b/proj/src/main/scala/com/typesafe/dbuild/project/build/BuildDirs.scala @@ -1,9 +1,8 @@ package com.typesafe.dbuild.project.build -import java.io.File import com.typesafe.dbuild.repo.core.GlobalDirs.buildDir -import com.typesafe.dbuild.adapter.Adapter -import Adapter.Path._ -import Adapter.syntaxio._ +import java.io.File +import sbt.io.Path.* +import sbt.io.syntax.* /** * dbuild-specific file names used during building. @@ -46,7 +45,7 @@ object BuildDirs { */ def localRepos(projectDir: File) = { val base = projectDir / dbuildDirName / inArtsDirName - Stream.from(0).map { level: Int => + Stream.from(0).map { (level: Int) => val repo = base / (level.toString) repo.mkdirs() repo diff --git a/proj/src/main/scala/com/typesafe/dbuild/project/build/LocalBuildRunner.scala b/proj/src/main/scala/com/typesafe/dbuild/project/build/LocalBuildRunner.scala index 6f27ec532..a9b29f985 100644 --- a/proj/src/main/scala/com/typesafe/dbuild/project/build/LocalBuildRunner.scala +++ b/proj/src/main/scala/com/typesafe/dbuild/project/build/LocalBuildRunner.scala @@ -1,18 +1,17 @@ package com.typesafe.dbuild.project.build -import com.typesafe.dbuild.model._ +import BuildDirs.* import com.typesafe.dbuild.logging.Logger -import com.typesafe.dbuild.project.resolve.ProjectResolver import Logger.prepareLogMsg -import java.io.File -import com.typesafe.dbuild.repo.core._ -import com.typesafe.dbuild.adapter.Adapter -import Adapter.Path._ -import com.typesafe.dbuild.project.dependencies.Extractor -import com.typesafe.dbuild.project.cleanup.Recycling.{ updateTimeStamp, markSuccess } +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.project.BuildData +import com.typesafe.dbuild.project.cleanup.Recycling.{ updateTimeStamp, markSuccess } +import com.typesafe.dbuild.project.dependencies.Extractor +import com.typesafe.dbuild.project.resolve.ProjectResolver +import com.typesafe.dbuild.repo.core.* import com.typesafe.dbuild.utils.TrackedProcessBuilder -import BuildDirs._ +import java.io.File +import sbt.io.Path.* /** * This class encodes the logic to resolve a project and run its build given @@ -95,7 +94,7 @@ object LocalBuildRunner { a.info.organization == "org.scala-lang" && a.info.name == "scala-library" } val libVersion = scalaLib flatMap { lib => - if (outProjects exists { p: Project => + if (outProjects exists { (p: Project) => p.organization == "org.scala-lang" && p.name.startsWith("scala") }) Some(lib.version) else None } diff --git a/proj/src/main/scala/com/typesafe/dbuild/project/dependencies/Extractor.scala b/proj/src/main/scala/com/typesafe/dbuild/project/dependencies/Extractor.scala index 7de72490c..767ebb861 100644 --- a/proj/src/main/scala/com/typesafe/dbuild/project/dependencies/Extractor.scala +++ b/proj/src/main/scala/com/typesafe/dbuild/project/dependencies/Extractor.scala @@ -1,25 +1,23 @@ package com.typesafe.dbuild.project.dependencies -import com.typesafe.dbuild.adapter.Adapter -import Adapter.IO -import Adapter.Path._ -import java.io.File -import com.typesafe.dbuild.project.resolve.ProjectResolver -import com.typesafe.dbuild.model.{ ProjectConfigAndExtracted, ProjectBuildConfig, ExtractedBuildMeta, SeqDepsModifiers } -import com.typesafe.dbuild.model.{ ExtractionOK, ExtractionOutcome, ExtractionFailed, ExtractionConfig, DepsModifiers } -import com.typesafe.dbuild.logging._ -import com.typesafe.dbuild.repo.core.Repository -import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } +import com.typesafe.dbuild.logging.* import com.typesafe.dbuild.logging.Logger.prepareLogMsg -import org.apache.ivy.core.module.id.ModuleId -import com.typesafe.dbuild.model.ProjectRef -import com.typesafe.dbuild.project.cleanup.Recycling.{ updateTimeStamp, markSuccess } import com.typesafe.dbuild.model.ExtractionOK -import org.apache.ivy.core.module.id.ModuleRevisionId import com.typesafe.dbuild.model.ProjectRef -import com.typesafe.dbuild.model.SeqDepsModifiersH._ -import com.typesafe.dbuild.model.SeqStringH._ +import com.typesafe.dbuild.model.SeqDepsModifiersH.* +import com.typesafe.dbuild.model.SeqStringH.* +import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } +import com.typesafe.dbuild.model.{ ExtractionOK, ExtractionOutcome, ExtractionFailed, ExtractionConfig, DepsModifiers } +import com.typesafe.dbuild.model.{ ProjectConfigAndExtracted, ProjectBuildConfig, ExtractedBuildMeta, SeqDepsModifiers } +import com.typesafe.dbuild.project.cleanup.Recycling.{ updateTimeStamp, markSuccess } +import com.typesafe.dbuild.project.resolve.ProjectResolver +import com.typesafe.dbuild.repo.core.Repository import com.typesafe.dbuild.utils.TrackedProcessBuilder +import java.io.File +import org.apache.ivy.core.module.id.ModuleId +import org.apache.ivy.core.module.id.ModuleRevisionId +import sbt.io.IO +import sbt.io.Path.* /** This is used to extract dependencies from projects. */ class Extractor( @@ -79,7 +77,7 @@ class Extractor( // extractor.dependencyExtractor.resolve() also resolves the nested ones, recursively logger.debug("Resolving " + build.name + " in " + dir.getAbsolutePath) val config = ExtractionConfig(dependencyExtractor.resolve(extractionConfig.buildConfig, dir, this, logger)) - config.buildConfig.getCommit foreach { s: String => logger.info("Commit: " + s) } + config.buildConfig.getCommit foreach { (s: String) => logger.info("Commit: " + s) } logger.debug("Repeatable Config: " + writeValue(config)) val outcome = extractedResolvedWithCache(config, tracker, dir, logger, debug) outcome match { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index eced445ba..d9f62c679 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -3,10 +3,12 @@ import Keys.* object Dependencies { - val scala210 = "2.10.7" val scala212 = "2.12.21" + val scala3 = "3.8.4" val mvnVersion = "3.5.2" + val gigahorse = "com.eed3si9n" %% "gigahorse-apache-http" % "0.9.4" + val typesafeConfig = "com.typesafe" % "config" % "1.2.1" val aetherVersion = "1.1.0" @@ -26,8 +28,14 @@ object Dependencies { val mvnWagon = "org.apache.maven.wagon" % "wagon-http" % "3.0.0" val mvnEmbedder = "org.apache.maven" % "maven-embedder" % mvnVersion - val jacks = "com.cunei" %% "jacks" % "2.2.5" - val jackson = "com.fasterxml.jackson.core" % "jackson-annotations" % "2.2.3" + val circeVersion = "0.14.16" + val circeCore = "io.circe" %% "circe-core" % circeVersion + val circeGeneric = "io.circe" %% "circe-generic" % circeVersion + // circe-generic-extras is only needed on Scala 2.12: on Scala 3 we use circe-core's own + // native io.circe.derivation.{ConfiguredEncoder,ConfiguredDecoder} instead (see CirceDerivationCompat). + def circeGenericExtras(scala212: Boolean): Option[ModuleID] = + if (scala212) Some("io.circe" %% "circe-generic-extras" % "0.14.4") else None + val circeParser = "io.circe" %% "circe-parser" % circeVersion val aws = "com.amazonaws" % "aws-java-sdk" % "1.3.29" val uriutil = "org.eclipse.equinox" % "org.eclipse.equinox.common" % "3.6.0.v20100503" val jline = "jline" % "jline" % "2.14.2" @@ -37,7 +45,7 @@ object Dependencies { val commonsIO = "commons-io" % "commons-io" % "2.4" val jsch = "com.jcraft" % "jsch" % "0.1.50" val oro = "org.apache.servicemix.bundles" % "org.apache.servicemix.bundles.oro" % "2.0.8_6" - val scallop = "org.rogach" %% "scallop" % "1.0.0" + val scallop = "org.rogach" %% "scallop" % "6.0.0" val jgit = "org.eclipse.jgit" % "org.eclipse.jgit" % "3.1.0.201310021548-r" @@ -46,56 +54,44 @@ object Dependencies { // these dependencies change depending on the scala version - def akkaActor(scala212: Boolean) = - if (scala212) - "com.typesafe.akka" %% "akka-actor" % "2.4.17" - else - "com.typesafe.akka" %% "akka-actor" % "2.3.16" + def akkaActor(scala212: Boolean): Option[ModuleID] = + Some("org.apache.pekko" %% "pekko-actor" % "1.7.0") def specs2(scala212: Boolean) = if (scala212) "org.specs2" %% "specs2-core" % "3.8.8" % "test" else - "org.specs2" %% "specs2" % "2.1.1" % "test" + "org.specs2" %% "specs2-core" % "5.9.1" % "test" def specs2It(scala212: Boolean) = if (scala212) "org.specs2" %% "specs2-core" % "3.8.8" % "it" else - "org.specs2" %% "specs2" % "2.1.1" % "it" - - def dispatch(scala212: Boolean) = - if (scala212) - "net.databinder.dispatch" %% "dispatch-core" % "0.12.2" - else - "net.databinder.dispatch" %% "dispatch-core" % "0.11.3" + "org.specs2" %% "specs2-core" % "5.9.1" % "it" // Once new versions of sbt/launcher/libraryManagement/zinc etc are released, move the 2.12 dependencies to those versions // The sbt modules dependend on both the scala version and the sbt version; sometimes they are "provided", but not always def sbtIo(scala212: Boolean, v: String) = - if (scala212) - "org.scala-sbt" %% "io" % "1.0.2" - else - "org.scala-sbt" % "io" % v + "org.scala-sbt" %% "io" % "1.12.2" def sbtIvy(scala212: Boolean, v: String) = if (scala212) "org.scala-sbt" %% "librarymanagement-ivy" % "1.0.4" else - "org.scala-sbt" % "ivy" % v + "org.scala-sbt" %% "librarymanagement-ivy" % v def sbtLogging(scala212: Boolean, v: String) = if (scala212) "org.scala-sbt" %% "util-logging" % "1.0.3" else - "org.scala-sbt" % "logging" % v + "org.scala-sbt" %% "util-logging" % v def sbtCommand(scala212: Boolean, v: String) = if (scala212) "org.scala-sbt" %% "command" % v else - "org.scala-sbt" % "command" % v + "org.scala-sbt" %% "command" % v def sbtSbt(scala212: Boolean, v: String) = if (scala212) @@ -105,18 +101,20 @@ object Dependencies { def sbtLauncherInt(scala212: Boolean, v:String) = "org.scala-sbt" % "launcher-interface" % "1.6.2" - // other dependencies that depend on whether scala is 2.10 or 2.12, but are only included in some cases - def zincIf212(scala212: Boolean, v:String): Option[ModuleID] = + // Official sbt-team compatibility shim, providing a unified API for sbt plugins that + // cross-build for sbt 1.x and sbt 2.x (see https://github.com/sbt/sbt2-compat). + def sbt2Compat(scala212: Boolean, v: String): ModuleID = if (scala212) - Some("org.scala-sbt" %% "zinc" % "1.0.5") + "com.github.sbt" % "sbt2-compat_2.12_1.0" % "0.2.0" else - None + "com.github.sbt" % "sbt2-compat_sbt2_3" % "0.2.0" - def gpgLibIf210(scala212: Boolean): Option[ModuleID] = + // other dependencies that depend on whether scala is 2.10 or 2.12, but are only included in some cases + def zincIf212(scala212: Boolean, v:String): Option[ModuleID] = if (scala212) - None + Some("org.scala-sbt" %% "zinc" % "1.0.5") else - Some("com.jsuereth" %% "gpg-library" % "0.8.2") + Some("org.scala-sbt" %% "zinc-classpath" % "2.0.4") def scalaXmlAlways = libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always diff --git a/project/LocalPlugin.scala b/project/LocalPlugin.scala new file mode 100644 index 000000000..01286991e --- /dev/null +++ b/project/LocalPlugin.scala @@ -0,0 +1,14 @@ +import sbt.{ *, given } +import Keys.* + +object LocalPlugin extends AutoPlugin { + override def trigger = allRequirements + override lazy val projectSettings = Seq( + Compile / scalacOptions ++= { + scalaBinaryVersion.value match { + case "2.12" => Vector("-Xsource:3", "--release:8") + case _ => Vector.empty + } + } + ) +} diff --git a/project/SbtSupport.scala b/project/SbtSupport.scala index ddc180cf5..b10cbbff6 100644 --- a/project/SbtSupport.scala +++ b/project/SbtSupport.scala @@ -1,13 +1,13 @@ -import sbt._ -import Keys._ +import sbt.* +import Keys.* object SbtSupport { val sbtLaunchJarUrl = SettingKey[String]("sbt-launch-jar-url") val sbtLaunchJarLocation = SettingKey[File]("sbt-launch-jar-location") val sbtLaunchJar = TaskKey[Seq[java.io.File]]("sbt-launch-jar", "Resolves sbt launch jar") - val buildSettings: Seq[Setting[_]] = Nil - val settings: Seq[Setting[_]] = buildSettings ++ Seq( + val buildSettings: Seq[Setting[?]] = Nil + val settings: Seq[Setting[?]] = buildSettings ++ Seq( // The jar is added as a resource, so that the running dbuild can find it and use it to spawn new instances of sbt (Compile / resourceGenerators) += sbtLaunchJar.taskValue ) diff --git a/project/packaging.scala b/project/packaging.scala index 8e2556298..1fe8e641c 100644 --- a/project/packaging.scala +++ b/project/packaging.scala @@ -1,15 +1,15 @@ -import sbt._ -import com.typesafe.sbt.packager.Keys._ -import sbt.Keys._ -import com.typesafe.sbt.SbtNativePackager._ -import scala.concurrent.ExecutionContext.Implicits.global import DbuildLauncher.{launcherVersion,uri} +import com.typesafe.sbt.SbtNativePackager.* +import com.typesafe.sbt.packager.Keys.* +import sbt.* +import sbt.Keys.* +import scala.concurrent.ExecutionContext.Implicits.global object Packaging { lazy val cleanMsg = TaskKey[Unit]("cleanMsg") - def mapArt[A](key:sbt.TaskKey[_], kind: String) = + def mapArt[A](key:sbt.TaskKey[?], kind: String) = // cheat a little: by setting the classifier to the version number, we can // publish to Ivy the tgz/zip with the full name, like "dbuild-0.8.1.tgz". Universal / key / artifact := @@ -18,7 +18,7 @@ object Packaging { .withExtension(kind) .withClassifier(Some(version.value)) - def settings(build:Project, repo:Project): Seq[Setting[_]] = + def settings(build:Project, repo:Project): Seq[Setting[?]] = SbtSupport.buildSettings ++ Seq( organization := "com.typesafe.dbuild", name := "dbuild", @@ -69,7 +69,7 @@ object Packaging { if(!tdir.exists) tdir.mkdirs() val file = tdir / "dbuild-launcher.jar" log.info("Downloading dbuild launcher "+ uri +" to "+ file.getAbsolutePath() +"...") - import scala.sys.process._ + import scala.sys.process.* url(uri).#>(file).! file -> "bin/dbuild-launcher.jar" } diff --git a/repo/src/main/scala/com/typesafe/dbuild/repo/core/CachedRepository.scala b/repo/src/main/scala/com/typesafe/dbuild/repo/core/CachedRepository.scala index c78f2d648..3af70cafa 100644 --- a/repo/src/main/scala/com/typesafe/dbuild/repo/core/CachedRepository.scala +++ b/repo/src/main/scala/com/typesafe/dbuild/repo/core/CachedRepository.scala @@ -1,16 +1,14 @@ package com.typesafe.dbuild.repo.core -import java.io.File -import com.typesafe.dbuild.adapter.Adapter -import com.typesafe.dbuild.http._ -import Adapter.IO -import Adapter.Path._ import com.typesafe.dbuild.adapter.Defaults -import dispatch.{url => dispUrl, Http} -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent._ -import scala.concurrent.duration._ +import com.typesafe.dbuild.http.* +import java.io.File import org.apache.commons.io.{ FileUtils, IOUtils } +import sbt.io.IO +import sbt.io.Path.* +import scala.concurrent.* +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration.* /** A cached remote repository. */ class CachedRemoteReadableRepository(cacheDir: File, uri: String) extends ReadableRepository { @@ -61,7 +59,7 @@ final class CachedRemoteRepository(cacheDir: File, uri: String, credentials: Cre /** Helpers for free-form HTTP repositories */ object Remote { - def push(uri: String, file: File, cred: Credentials, timeOut: Duration = 20 minutes): Unit = { + def push(uri: String, file: File, cred: Credentials, timeOut: Duration = 20.minutes): Unit = { val ht = new HttpTransfer(Defaults.version) try { ht.upload(uri, file, cred)(println) @@ -69,7 +67,7 @@ object Remote { ht.close() } } - def pull(uri: String, local: File, timeOut: Duration = 20 minutes): Unit = { + def pull(uri: String, local: File, timeOut: Duration = 20.minutes): Unit = { // Ensure directory exists. local.getParentFile.mkdirs() val ht = new HttpTransfer(Defaults.version) diff --git a/repo/src/main/scala/com/typesafe/dbuild/repo/core/GlobalDirs.scala b/repo/src/main/scala/com/typesafe/dbuild/repo/core/GlobalDirs.scala index c5c0b2ffc..a5690a681 100644 --- a/repo/src/main/scala/com/typesafe/dbuild/repo/core/GlobalDirs.scala +++ b/repo/src/main/scala/com/typesafe/dbuild/repo/core/GlobalDirs.scala @@ -1,9 +1,9 @@ package com.typesafe.dbuild.repo.core -import com.typesafe.dbuild.model._ +import com.typesafe.dbuild.adapter.Defaults +import com.typesafe.dbuild.model.* import java.io.File -import com.typesafe.dbuild.adapter.{Adapter,Defaults} -import Adapter.Path._ +import sbt.io.Path.* // TODO - Locally configured area for projects // With some kind of locking to prevent more than one @@ -71,9 +71,10 @@ object GlobalDirs { def checkForObsoleteDirs(f: (=> String) => Unit) = { def issueWarnings(root: File, baseDirName: String, dirName: String) = { - import Adapter.{ FileFilter => FF, DirectoryFilter => DF, toFF } - import Adapter.syntaxio._ - root.*(DF && ((toFF(baseDirName)) || toFF(baseDirName + "-*")) && -(toFF(dirName))).get.foreach { z => + import sbt.io.FileFilter.{ globFilter => toFF } + import sbt.io.syntax.* + import sbt.io.{ FileFilter => FF, DirectoryFilter => DF } + root.*(DF && ((toFF(baseDirName)) || toFF(baseDirName + "-*")) && -(toFF(dirName))).get().foreach { z => f("WARNING: This directory is not in use: " + z.getCanonicalPath) } } diff --git a/repo/src/main/scala/com/typesafe/dbuild/repo/core/Helper.scala b/repo/src/main/scala/com/typesafe/dbuild/repo/core/Helper.scala index 5fefce17b..e35c645bf 100644 --- a/repo/src/main/scala/com/typesafe/dbuild/repo/core/Helper.scala +++ b/repo/src/main/scala/com/typesafe/dbuild/repo/core/Helper.scala @@ -1,14 +1,13 @@ package com.typesafe.dbuild.repo.core -import com.typesafe.dbuild.model._ -import java.io.File -import com.typesafe.dbuild.adapter.Adapter -import Adapter.{ RichFile, IO, Path } -import Adapter.syntaxio._ -import Path._ -import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } -import com.typesafe.dbuild.logging.Logger import com.typesafe.dbuild.hashing +import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } +import java.io.File +import sbt.io.syntax.* +import sbt.io.{ RichFile, IO, Path } +import Path.* object LocalRepoHelper { @@ -37,8 +36,8 @@ object LocalRepoHelper { // we use publishMeta() when we need to publish some data that may already be // in the remote repository (for instance: repeatable build info, or repeatable project info) - def publishMeta[T <: { def uuid: String }](data: T, remote: Repository, - makeKey: String => String, log: Logger)(implicit m: scala.reflect.Manifest[T]): Unit = + def publishMeta[T <: HasUuid](data: T, remote: Repository, + makeKey: String => String, log: Logger)(implicit encT: io.circe.Encoder[T], decT: io.circe.Decoder[T]): Unit = IO.withTemporaryFile("meta-data", data.uuid) { file => val key = makeKey(data.uuid) // is the file already there? We might try to publish twice, as a previous run @@ -166,7 +165,7 @@ object LocalRepoHelper { } def materializeProjectMetadata(uuid: String, remote: ReadableRepository): ProjectArtifactInfo = { - def getMeta[T](makeMeta: String => String)(implicit m: scala.reflect.Manifest[T]) = { + def getMeta[T](makeMeta: String => String)(implicit d: io.circe.Decoder[T]) = { val key = makeMeta(uuid) val file = remote get key try readValue[T](file) diff --git a/repo/src/main/scala/com/typesafe/dbuild/repo/core/OfflineLocalCacheRepository.scala b/repo/src/main/scala/com/typesafe/dbuild/repo/core/OfflineLocalCacheRepository.scala index 0fb391e05..07957a6f3 100644 --- a/repo/src/main/scala/com/typesafe/dbuild/repo/core/OfflineLocalCacheRepository.scala +++ b/repo/src/main/scala/com/typesafe/dbuild/repo/core/OfflineLocalCacheRepository.scala @@ -1,7 +1,7 @@ package com.typesafe.dbuild.repo.core import java.io.File -import com.typesafe.dbuild.adapter.Adapter.IO +import sbt.io.IO /** A cached remote repository. */ class OfflineLocalCacheRepository(cacheDir: File) extends ReadableRepository { diff --git a/repo/src/main/scala/com/typesafe/dbuild/repo/core/ProjectRepoMain.scala b/repo/src/main/scala/com/typesafe/dbuild/repo/core/ProjectRepoMain.scala index 1ca8e9a96..653e453d0 100644 --- a/repo/src/main/scala/com/typesafe/dbuild/repo/core/ProjectRepoMain.scala +++ b/repo/src/main/scala/com/typesafe/dbuild/repo/core/ProjectRepoMain.scala @@ -1,13 +1,12 @@ package com.typesafe.dbuild.repo.core -import com.typesafe.dbuild.model._ import LocalRepoHelper.ResolutionResult -import java.io.File -import com.typesafe.dbuild.adapter.Adapter -import Adapter.{ RichFile, IO, Path } -import Adapter.syntaxio._ -import Path._ +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.model.Utils.writeValue +import java.io.File +import sbt.io.syntax.* +import sbt.io.{ RichFile, IO, Path } +import Path.* /** Expose for SBT launcher support. */ class SbtRepoMain extends xsbti.AppMain { diff --git a/repo/src/main/scala/com/typesafe/dbuild/repo/core/Repository.scala b/repo/src/main/scala/com/typesafe/dbuild/repo/core/Repository.scala index 953402e01..7d6372067 100644 --- a/repo/src/main/scala/com/typesafe/dbuild/repo/core/Repository.scala +++ b/repo/src/main/scala/com/typesafe/dbuild/repo/core/Repository.scala @@ -1,10 +1,9 @@ package com.typesafe.dbuild.repo.core -import java.io.File -import com.typesafe.dbuild.adapter.Adapter import com.typesafe.dbuild.http.Credentials -import Adapter.IO -import Adapter.Path._ +import java.io.File +import sbt.io.IO +import sbt.io.Path.* /** Interface for a repository of raw key-value files. */ trait ReadableRepository { diff --git a/support/src/it/scala/com/typesafe/dbuild/support/sbt/SbtExtractionSpec.scala b/support/src/it/scala/com/typesafe/dbuild/support/sbt/SbtExtractionSpec.scala index 880668d44..6b8d6fdd5 100644 --- a/support/src/it/scala/com/typesafe/dbuild/support/sbt/SbtExtractionSpec.scala +++ b/support/src/it/scala/com/typesafe/dbuild/support/sbt/SbtExtractionSpec.scala @@ -22,14 +22,14 @@ object SbtExtractionSpec extends Specification { writePluginsFile(projectDir)( """|organization := "test" | - |name := "test-build" + |LocalRootProject / name := "test-build" | |libraryDependencies += "com.typesafe" % "config" % "1.2.1" |""".stripMargin) writeBuildFile(projectDir)("scalaVersion := \"" + Defaults.scalaVersion + "\"" + ("""| | - |name := "test" + |LocalRootProject / name := "test" | |version := "1.0" | @@ -77,10 +77,10 @@ object SbtExtractionSpec extends Specification { ) buildProjects.projects.flatMap(_.dependencies) must containAllOf( // TODO - Why isn't sbt showing up as a dependency? - Seq(ProjectRef("scala-library", "org.scala-lang"), ProjectRef("config", "com.typesafe")) + Seq(ProjectRef("scala3-library", "org.scala-lang"), ProjectRef("config", "com.typesafe")) ) prodProjects.projects.flatMap(_.dependencies) must containAllOf( - Seq(ProjectRef("scala-library", "org.scala-lang"), ProjectRef("junit", "junit")) + Seq(ProjectRef("scala3-library", "org.scala-lang"), ProjectRef("junit", "junit")) ) } } diff --git a/support/src/it/scala/com/typesafe/dbuild/support/sbt/SbtTestHarness.scala b/support/src/it/scala/com/typesafe/dbuild/support/sbt/SbtTestHarness.scala index 45e0168f1..5d85be2d7 100644 --- a/support/src/it/scala/com/typesafe/dbuild/support/sbt/SbtTestHarness.scala +++ b/support/src/it/scala/com/typesafe/dbuild/support/sbt/SbtTestHarness.scala @@ -1,5 +1,5 @@ package com.typesafe.dbuild.support.sbt -import com.typesafe.dbuild.adapter.Adapter.IO +import sbt.io.IO import java.io.File import java.net.URL diff --git a/support/src/main/scala/com/typesafe/dbuild/support/SbtUtil.scala b/support/src/main/scala/com/typesafe/dbuild/support/SbtUtil.scala index b502b5291..479af1f50 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/SbtUtil.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/SbtUtil.scala @@ -1,9 +1,9 @@ package com.typesafe.dbuild.support +import _root_.java.util.{ Map => JavaMap } +import _root_.sbt.librarymanagement.{ ModuleID => SbtModuleID, Artifact => SbtArtifact } +import _root_.scala.jdk.CollectionConverters.* import com.typesafe.dbuild.model.SbtPluginAttrs -import com.typesafe.dbuild.adapter.Adapter.{ ModuleID => SbtModuleID, Artifact => SbtArtifact } import org.apache.ivy.core.module.id.{ ModuleRevisionId => IvyModuleRevisionId } -import _root_.scala.collection.JavaConversions._ -import _root_.java.util.{ Map => JavaMap } object SbtUtil { /** @@ -15,7 +15,7 @@ object SbtUtil { def pluginAttrs(m: SbtArtifact): Option[SbtPluginAttrs] = pluginAttrs(m.extraAttributes) def pluginAttrs(mr: IvyModuleRevisionId): Option[SbtPluginAttrs] = - pluginAttrs(mr.getExtraAttributes.asInstanceOf[JavaMap[String, String]].toMap) + pluginAttrs(mr.getExtraAttributes.asInstanceOf[JavaMap[String, String]].asScala.toMap) def pluginAttrs(attrs: Map[String, String]): Option[SbtPluginAttrs] = { if (attrs.contains("e:sbtVersion") && attrs.contains("e:scalaVersion")) Some(SbtPluginAttrs(attrs("e:sbtVersion"), attrs("e:scalaVersion"))) diff --git a/support/src/main/scala/com/typesafe/dbuild/support/aether/AetherBuildSystem.scala b/support/src/main/scala/com/typesafe/dbuild/support/aether/AetherBuildSystem.scala index 5f78212d0..45d2700fd 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/aether/AetherBuildSystem.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/aether/AetherBuildSystem.scala @@ -16,49 +16,48 @@ package com.typesafe.dbuild.support.aether * ******************************************************************************* */ -import com.typesafe.dbuild.support.BuildSystemCore +import _root_.java.io.{ FileReader, FileWriter } +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* +import _root_.sbt.io.{ IO, ExactFilter } +import collection.JavaConverters.* +import com.typesafe.dbuild.adapter.Adapter.allPaths +import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.manifest.ModuleAttributes +import com.typesafe.dbuild.manifest.ModuleInfo +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.Utils.readValue +import com.typesafe.dbuild.project.build.LocalBuildRunner +import com.typesafe.dbuild.project.dependencies.Extractor import com.typesafe.dbuild.project.{ BuildSystem, BuildData } -import com.typesafe.dbuild.model._ +import com.typesafe.dbuild.repo.core.GlobalDirs.dbuildHomeDir import com.typesafe.dbuild.repo.core.LocalArtifactMissingException +import com.typesafe.dbuild.repo.core.LocalRepoHelper +import com.typesafe.dbuild.support.BuildSystemCore +import com.typesafe.dbuild.support.NameFixer.fixName +import com.typesafe.dbuild.support.SbtUtil.pluginAttrs +import com.typesafe.dbuild.support.assemble.AssembleBuildSystem +import com.typesafe.dbuild.support.assemble.NamePatcher +import com.typesafe.dbuild.support.assemble.OrgNameVerFilenamesuffix +import com.typesafe.dbuild.support.ivy.IvyMachinery.PublishIvyInfo import com.typesafe.dbuild.utils.TrackedProcessBuilder import java.io.File -import com.typesafe.dbuild.adapter.Adapter -import Adapter.Path._ -import Adapter.{IO,allPaths} -import Adapter.syntaxio._ -import com.typesafe.dbuild.logging.Logger -import sys.process._ -import com.typesafe.dbuild.repo.core.LocalRepoHelper -import com.typesafe.dbuild.model.Utils.readValue -import xsbti.Predefined._ -import collection.JavaConversions._ -import collection.JavaConverters._ -import _root_.java.io.{ FileReader, FileWriter } import org.apache.ivy import ivy.Ivy -import ivy.plugins.resolver.{ BasicResolver, ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver } -import ivy.core.settings.IvySettings import ivy.core.module.descriptor.{ DefaultModuleDescriptor, DefaultDependencyDescriptor, Artifact } -import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter import ivy.core.module.id.{ ModuleId, ModuleRevisionId } -import ivy.core.resolve.{ ResolveEngine, ResolveOptions } import ivy.core.report.ResolveReport -import org.apache.ivy.core.resolve.IvyNode -import com.typesafe.dbuild.support.NameFixer.fixName +import ivy.core.resolve.{ ResolveEngine, ResolveOptions } +import ivy.core.settings.IvySettings +import ivy.plugins.resolver.{ BasicResolver, ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver } import org.apache.ivy.core.module.descriptor.DefaultArtifact -import com.typesafe.dbuild.support.ivy.IvyMachinery.PublishIvyInfo -import com.typesafe.dbuild.project.dependencies.Extractor -import com.typesafe.dbuild.project.build.LocalBuildRunner -import com.typesafe.dbuild.repo.core.GlobalDirs.dbuildHomeDir -import com.typesafe.dbuild.support.SbtUtil.pluginAttrs -import com.typesafe.dbuild.support.assemble.AssembleBuildSystem -import com.typesafe.dbuild.support.assemble.NamePatcher -import com.typesafe.dbuild.support.assemble.OrgNameVerFilenamesuffix -import com.typesafe.dbuild.manifest.ModuleAttributes -import com.typesafe.dbuild.manifest.ModuleInfo +import org.apache.ivy.core.resolve.IvyNode +import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter +import sys.process.* +import xsbti.Predefined.* -import org.apache.maven.model.{ Model, Dependency } import org.apache.maven.model.io.xpp3.{ MavenXpp3Reader, MavenXpp3Writer } +import org.apache.maven.model.{ Model, Dependency } import org.eclipse.aether.DefaultRepositorySystemSession import org.eclipse.aether.RepositorySystem @@ -68,8 +67,8 @@ import org.eclipse.aether.artifact.{ DefaultArtifact => AetherDefaultArtifact } import org.eclipse.aether.collection.CollectRequest import org.eclipse.aether.deployment.DeployRequest import org.eclipse.aether.deployment.DeploymentException -import org.eclipse.aether.graph.{ Dependency => AetherDependency } import org.eclipse.aether.graph.DependencyNode +import org.eclipse.aether.graph.{ Dependency => AetherDependency } import org.eclipse.aether.installation.InstallRequest import org.eclipse.aether.installation.InstallationException import org.eclipse.aether.repository.Authentication @@ -77,12 +76,11 @@ import org.eclipse.aether.repository.LocalRepository import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.resolution.DependencyRequest import org.eclipse.aether.resolution.DependencyResolutionException +import org.eclipse.aether.transfer.ArtifactNotFoundException import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator import org.eclipse.aether.util.repository.AuthenticationBuilder -import org.eclipse.aether.transfer.ArtifactNotFoundException import org.apache.maven.repository.internal.MavenRepositorySystemUtils -import org.eclipse.aether.RepositorySystem import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory import org.eclipse.aether.impl.DefaultServiceLocator import org.eclipse.aether.spi.connector.RepositoryConnectorFactory @@ -92,10 +90,9 @@ import org.eclipse.aether.transport.http.HttpTransporterFactory import org.eclipse.aether.AbstractRepositoryListener import org.eclipse.aether.RepositoryEvent -import org.eclipse.aether.resolution.ArtifactRequest -import org.eclipse.aether.graph.DependencyNode import org.eclipse.aether.resolution.ArtifactDescriptorRequest import org.eclipse.aether.resolution.ArtifactDescriptorResult +import org.eclipse.aether.resolution.ArtifactRequest class ConsoleRepositoryListener(log: Logger) extends AbstractRepositoryListener { override def artifactDeployed(event: RepositoryEvent): Unit = @@ -168,9 +165,7 @@ import org.eclipse.aether.transfer.TransferResource * A simplistic transfer listener that logs uploads/downloads to the console. */ class ConsoleTransferListener(log: Logger) extends AbstractTransferListener { - // we cannot use scala.collection.concurrent.Map (TrieMap), as this code must compile under 2.9 as well - private val downloads = - new scala.collection.mutable.HashMap[TransferResource, Long] with scala.collection.mutable.SynchronizedMap[TransferResource, Long] + private val downloads = new scala.collection.concurrent.TrieMap[TransferResource, Long] override def transferInitiated(event: TransferEvent): Unit = { val message = if (event.getRequestType() == TransferEvent.RequestType.PUT) "Trying to upload" else "Trying to download" @@ -248,7 +243,7 @@ object Booter { locator.addService(classOf[TransporterFactory], classOf[FileTransporterFactory]) locator.addService(classOf[TransporterFactory], classOf[HttpTransporterFactory]) locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler() { - override def serviceCreationFailed(`type`: Class[_], impl: Class[_], exception: Throwable): Unit = + override def serviceCreationFailed(`type`: Class[?], impl: Class[?], exception: Throwable): Unit = { exception.printStackTrace(); } @@ -278,7 +273,7 @@ object Booter { /** Implementation of the Aether build system. workingDir is the "target" general dbuild dir */ class AetherBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends BuildSystemCore { - import Booter._ + import Booter.* val name = "aether" type ExtraType = AetherExtraConfig @@ -322,7 +317,7 @@ class AetherBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends getJar: Boolean, getSource: Boolean, getJavadoc: Boolean, rematerializedRepo: Option[File], log: Logger) = { - import Booter._ + import Booter.* // we resolve directly to the desired output location val localRepository = new LocalRepository(localRepo) @@ -388,14 +383,14 @@ class AetherBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends val sourceArt = getArtifact("jar", "sources") val javadocArt = getArtifact("jar", "javadoc") - val descriptorRequest = new ArtifactDescriptorRequest(jarArt, mavenRepositories, null) + val descriptorRequest = new ArtifactDescriptorRequest(jarArt, mavenRepositories.asJava, null) val descriptorResult = repositorySystem.readArtifactDescriptor(session, descriptorRequest) val pomOrigin = descriptorResult.getRepository // will be null if it didn't resolve if (pomOrigin == null) failure() def grab(doGrab: Boolean, inArt: AetherArtifact): Option[AetherArtifact] = if (!doGrab) None else Some({ - val request = new ArtifactRequest(inArt, mavenRepositories, null) + val request = new ArtifactRequest(inArt, mavenRepositories.asJava, null) val outArtifact = repositorySystem.resolveArtifact(session, request).getArtifact val file = outArtifact.getFile if (file == null) failure() @@ -418,7 +413,7 @@ class AetherBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends baseDir: File, extractor: Extractor, log: Logger, debug: Boolean): ExtractedBuildMeta = { val config = extractionConfig.buildConfig val extra = config.getExtra[AetherExtraConfig] - import extra._ + import extra.* val module = config.uri.substring(7) val modRevId = ModuleRevisionId.parse(module) @@ -426,7 +421,7 @@ class AetherBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends // we grab the pom only, directly in the extraction dir val (descriptorResult, arts) = resolveAether(modRevId, baseDir, mainJar, sources, javadoc, None, log) // only the direct dependencies - val dependencies = descriptorResult.getDependencies.toSeq + val dependencies = descriptorResult.getDependencies.asScala.toSeq if (dependencies.isEmpty) log.debug("There are no direct dependencies") else { @@ -454,7 +449,7 @@ class AetherBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends def runBuild(project: RepeatableProjectBuild, tracker: TrackedProcessBuilder, baseDir: File, input: BuildInput, localBuildRunner: LocalBuildRunner, buildData: BuildData): BuildArtifactsOut = { val extra = project.config.getExtra[AetherExtraConfig] - import extra._ + import extra.* val log = buildData.log log.debug("BuildInput is: " + input) @@ -520,12 +515,12 @@ class AetherBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends // the pom art will be the first one in "arts" val (descriptorResult, arts) = resolveAether(download, localRepo, mainJar, sources, javadoc, Some(availableRepo), log) // DELETE from the resolved local repository all files called "_remote.repositories", which are aether temporary leftovers - localRepo.**(new Adapter.ExactFilter("_remote.repositories")).get.foreach { IO.delete } + localRepo.**(new ExactFilter("_remote.repositories")).get().foreach { IO.delete } // TODO: add support for source/javadoc/etc jars, as well as plugins. log.debug("List of dependencies from the pom file:") - descriptorResult.getDependencies foreach { + descriptorResult.getDependencies.asScala foreach { d => log.debug(" " + d.toString) // TODO: inspect "checkMissing", and try to locate each dependency in // the availableArts list. If the combination of crossVersion and checkMissing @@ -613,7 +608,7 @@ class AetherBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends //BuildSubArtifactsOut(subProjName, artifacts, shas, moduleInfo) val q = BuildArtifactsOut(Seq(BuildSubArtifactsOut("default-aether-project", arts.map { aetherArtifactToArtifactLocation }, - allPaths(localRepo).get.filterNot(file => file.isDirectory) map { LocalRepoHelper.makeArtifactSha(_, localRepo) }, + allPaths(localRepo).get().filterNot(file => file.isDirectory) map { LocalRepoHelper.makeArtifactSha(_, localRepo) }, ModuleInfo(organization = finalModRevId.getOrganisation, name = trimName, version = finalModRevId.getRevision, { // We need to calculate CrossBuildProperties; that is made a bit complicated by the fact that diff --git a/support/src/main/scala/com/typesafe/dbuild/support/aether/ProjectResolver.scala b/support/src/main/scala/com/typesafe/dbuild/support/aether/ProjectResolver.scala index a65a26202..ae312746b 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/aether/ProjectResolver.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/aether/ProjectResolver.scala @@ -1,18 +1,18 @@ package com.typesafe.dbuild.support.aether +import _root_.sbt.io.FileFilter.{ globFilter => toFF } +import _root_.sbt.io.IO +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* import com.typesafe.dbuild.logging.Logger -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.adapter.Adapter -import Adapter.Path._ -import Adapter.{IO, toFF} -import Adapter.syntaxio._ +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.project.resolve.ProjectResolver import java.io.File -import org.apache.ivy.core.module.id.{ ModuleId, ModuleRevisionId } import java.text.DateFormat import java.util.Locale import java.util.TimeZone import javax.mail.internet.MailDateFormat +import org.apache.ivy.core.module.id.{ ModuleId, ModuleRevisionId } /** * This class knows how to resolve the 'aether' uri scheme. It usually doesn't @@ -31,7 +31,7 @@ class AetherProjectResolver(repos: List[xsbti.Repository]) extends ProjectResolv def resolve(config: ProjectBuildConfig, baseDir: File, log: Logger): ProjectBuildConfig = { // clean the directory content, just in case there are leftovers // (we do not really need to re-resolve non-snapshot artifacts, but let's be conservative) - IO.delete(baseDir.*(toFF("*")).get) + IO.delete(baseDir.*(toFF("*")).get()) val modRevId = AetherBuildSystem.getProjectModuleID(config) val revision = modRevId.getRevision diff --git a/support/src/main/scala/com/typesafe/dbuild/support/assemble/AssembleBuildSystem.scala b/support/src/main/scala/com/typesafe/dbuild/support/assemble/AssembleBuildSystem.scala index ccd295002..25b1c5ac8 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/assemble/AssembleBuildSystem.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/assemble/AssembleBuildSystem.scala @@ -1,34 +1,35 @@ package com.typesafe.dbuild.support.assemble -import com.typesafe.dbuild.model._ -import org.apache.commons.io.FilenameUtils -import org.apache.commons.io.FileUtils import _root_.java.io.File -import com.typesafe.dbuild.adapter.Adapter -import Adapter.Path._ -import Adapter.{IO,NameFilter,allPaths,toFF} -import Adapter.IO.relativize -import Adapter.syntaxio._ +import _root_.sbt.io.FileFilter.{ globFilter => toFF } +import _root_.sbt.io.IO.relativize +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* +import _root_.sbt.io.{ IO, NameFilter } +import collection.JavaConverters.* +import com.typesafe.dbuild.adapter.Adapter.allPaths +import com.typesafe.dbuild.hashing import com.typesafe.dbuild.logging.Logger -import sys.process._ -import com.typesafe.dbuild.repo.core.LocalRepoHelper +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.SeqDBCH.* import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } -import com.typesafe.dbuild.project.dependencies.Extractor -import com.typesafe.dbuild.project.build.LocalBuildRunner import com.typesafe.dbuild.project.BuildData import com.typesafe.dbuild.project.BuildSystem +import com.typesafe.dbuild.project.build.BuildDirs.localRepos +import com.typesafe.dbuild.project.build.LocalBuildRunner +import com.typesafe.dbuild.project.dependencies.Extractor +import com.typesafe.dbuild.repo.core.LocalRepoHelper import com.typesafe.dbuild.support.BuildSystemCore -import com.typesafe.dbuild.hashing -import collection.JavaConverters._ -import org.apache.maven.model.{ Model, Dependency } -import org.apache.maven.model.io.xpp3.{ MavenXpp3Reader, MavenXpp3Writer } -import org.apache.maven.model.Dependency -import org.apache.ivy.util.ChecksumHelper import com.typesafe.dbuild.support.NameFixer.fixName import com.typesafe.dbuild.utils.TrackedProcessBuilder +import org.apache.commons.io.FileUtils +import org.apache.commons.io.FilenameUtils import org.apache.ivy -import com.typesafe.dbuild.project.build.BuildDirs.localRepos -import com.typesafe.dbuild.model.SeqDBCH._ +import org.apache.ivy.util.ChecksumHelper +import org.apache.maven.model.Dependency +import org.apache.maven.model.io.xpp3.{ MavenXpp3Reader, MavenXpp3Writer } +import org.apache.maven.model.{ Model, Dependency } +import sys.process.* /** * The "assemble" build system accepts a list of nested projects, with the same format @@ -209,7 +210,7 @@ object AssembleBuildSystem extends BuildSystemCore { val localRepo = input.outRepo // We do a bunch of in-place file operations in the localRepo, before returning. // To avoid problems due to stale files, delete all contents before proceeding. - IO.delete(localRepo.*(toFF("*")).get) + IO.delete(localRepo.*(toFF("*")).get()) def mavenArtifactDir(repoDir: File, ref: ProjectRef, crossSuffix: String) = ref.organization.split('.').foldLeft(repoDir)(_ / _) / (ref.name + crossSuffix) @@ -228,7 +229,7 @@ object AssembleBuildSystem extends BuildSystemCore { val artCross = if (isScalaCoreRef(art)) "" else crossSuffix Seq(mavenArtifactDir(localRepo, art, artCross), ivyArtifactDir(localRepo, art, artCross)) - }.distinct.flatMap { allPaths(_).get }. + }.distinct.flatMap { allPaths(_).get() }. // Since this may be a real local maven repo, it also contains // the "maven-metadata-local.xml" files, which should /not/ end up in the repository. filterNot(file => file.isDirectory || file.getName == "maven-metadata-local.xml").map(f) @@ -262,7 +263,7 @@ object AssembleBuildSystem extends BuildSystemCore { tracker, Seq.empty, Seq.empty, BuildData(log.newNestedLogger(p.name, p.name), buildData.debug)) val artifactsOut = outcome match { case o: BuildGood => o.artsOut - case o: BuildBad => sys.error("Part " + p.name + ": " + o.status) + case o: BuildBad => sys.error("Part " + p.name + ": " + o.status()) } val q = (p.name, artifactsOut) log.debug("---> " + q) @@ -410,10 +411,10 @@ object AssembleBuildSystem extends BuildSystemCore { val allArtifactsOut = artifactsMap.map { _._2 } val available = allArtifactsOut.flatMap { _.results }.flatMap { _.artifacts } - (allPaths(localRepo).get).filter(_.getName.endsWith(".pom")).foreach { patchPomDependencies(_, available) } + (allPaths(localRepo).get()).filter(_.getName.endsWith(".pom")).foreach { patchPomDependencies(_, available) } val ivyHome = dir / ".ivy2" / "cache" - (allPaths(localRepo).get).filter(_.getName == "ivy.xml").foreach { patchIvyDependencies(_, available, ivyHome, localRepo) } + (allPaths(localRepo).get()).filter(_.getName == "ivy.xml").foreach { patchIvyDependencies(_, available, ivyHome, localRepo) } // dbuild SHAs must be re-computed (since the POM/Ivy files changed) // We preserve the list of original subprojects (and consequently modules), @@ -458,13 +459,13 @@ object AssembleBuildSystem extends BuildSystemCore { // Members declared in ivy.util.extendable.ExtendableItem def getAttribute(x: String): String = m.getAttribute(x) - def getAttributes(): java.util.Map[_, _] = + def getAttributes(): java.util.Map[?, ?] = m.getAttributes() def getExtraAttribute(x: String): String = m.getExtraAttribute(x) - def getExtraAttributes(): java.util.Map[_, _] = + def getExtraAttributes(): java.util.Map[?, ?] = m.getExtraAttributes() - def getQualifiedExtraAttributes(): java.util.Map[_, _] = + def getQualifiedExtraAttributes(): java.util.Map[?, ?] = m.getQualifiedExtraAttributes() // Members declared in ivy.core.module.descriptor.ModuleDescriptor def canExclude(): Boolean = @@ -493,9 +494,9 @@ object AssembleBuildSystem extends BuildSystemCore { m.getDependencies() def getDescription(): String = m.getDescription() - def getExtraAttributesNamespaces(): java.util.Map[_, _] = + def getExtraAttributesNamespaces(): java.util.Map[?, ?] = m.getExtraAttributesNamespaces() - def getExtraInfo(): java.util.Map[_, _] = + def getExtraInfo(): java.util.Map[?, ?] = m.getExtraInfo() def getHomePage(): String = m.getHomePage() @@ -543,8 +544,6 @@ object AssembleBuildSystem extends BuildSystemCore { } def patchIvyDependencies(file: File, available: Seq[ArtifactLocation], ivyHome: File, localRepo: File) = { - import _root_.scala.collection.JavaConversions._ - val settings = new ivy.core.settings.IvySettings() settings.setDefaultIvyUserDir(ivyHome) val parser = ivy.plugins.parser.xml.XmlModuleDescriptorParser.getInstance() @@ -622,7 +621,7 @@ object AssembleBuildSystem extends BuildSystemCore { val reader = new MavenXpp3Reader() val model = reader.read(new _root_.java.io.FileReader(pom)) // transform dependencies - val deps: Seq[Dependency] = model.getDependencies.asScala + val deps: Seq[Dependency] = model.getDependencies.asScala.toSeq val newDeps: _root_.java.util.List[Dependency] = (deps map { m => available.find { artifact => artifact.info.organization == m.getGroupId && diff --git a/support/src/main/scala/com/typesafe/dbuild/support/ivy/IvyBuildSystem.scala b/support/src/main/scala/com/typesafe/dbuild/support/ivy/IvyBuildSystem.scala index 069f741f8..58849db7d 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/ivy/IvyBuildSystem.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/ivy/IvyBuildSystem.scala @@ -1,37 +1,37 @@ package com.typesafe.dbuild.support.ivy -import com.typesafe.dbuild.support.BuildSystemCore +import _root_.sbt.io.IO +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* +import com.typesafe.dbuild.adapter.Adapter.allPaths +import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.Utils.readValue +import com.typesafe.dbuild.project.build.LocalBuildRunner +import com.typesafe.dbuild.project.dependencies.Extractor import com.typesafe.dbuild.project.{ BuildSystem, BuildData } -import com.typesafe.dbuild.model._ +import com.typesafe.dbuild.repo.core.GlobalDirs.dbuildHomeDir import com.typesafe.dbuild.repo.core.LocalArtifactMissingException +import com.typesafe.dbuild.repo.core.LocalRepoHelper +import com.typesafe.dbuild.support.BuildSystemCore +import com.typesafe.dbuild.support.NameFixer.fixName +import com.typesafe.dbuild.support.SbtUtil.pluginAttrs +import com.typesafe.dbuild.support.ivy.IvyMachinery.PublishIvyInfo import com.typesafe.dbuild.utils.TrackedProcessBuilder import java.io.File -import com.typesafe.dbuild.adapter.Adapter -import Adapter.Path._ -import Adapter.{IO,allPaths} -import Adapter.syntaxio._ -import com.typesafe.dbuild.logging.Logger -import sys.process._ -import com.typesafe.dbuild.repo.core.LocalRepoHelper -import com.typesafe.dbuild.model.Utils.readValue -import xsbti.Predefined._ import org.apache.ivy import ivy.Ivy -import ivy.plugins.resolver.{ BasicResolver, ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver } -import ivy.core.settings.IvySettings import ivy.core.module.descriptor.{ DefaultModuleDescriptor, DefaultDependencyDescriptor, Artifact } -import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter import ivy.core.module.id.{ ModuleId, ModuleRevisionId } -import ivy.core.resolve.{ ResolveEngine, ResolveOptions } import ivy.core.report.ResolveReport -import org.apache.ivy.core.resolve.IvyNode -import com.typesafe.dbuild.support.NameFixer.fixName +import ivy.core.resolve.{ ResolveEngine, ResolveOptions } +import ivy.core.settings.IvySettings +import ivy.plugins.resolver.{ BasicResolver, ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver } import org.apache.ivy.core.module.descriptor.DefaultArtifact -import com.typesafe.dbuild.support.ivy.IvyMachinery.PublishIvyInfo -import com.typesafe.dbuild.project.dependencies.Extractor -import com.typesafe.dbuild.project.build.LocalBuildRunner -import com.typesafe.dbuild.repo.core.GlobalDirs.dbuildHomeDir -import com.typesafe.dbuild.support.SbtUtil.pluginAttrs +import org.apache.ivy.core.resolve.IvyNode +import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter +import sys.process.* +import xsbti.Predefined.* /** Implementation of the Ivy build system. workingDir is the "target" general dbuild dir */ class IvyBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends BuildSystemCore { @@ -62,13 +62,13 @@ class IvyBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends Bu } else { val modRevId = artifactReports(0).getArtifact.getModuleRevisionId val module = modRevId.getModuleId() - import scala.collection.JavaConversions._ + import scala.jdk.CollectionConverters.* def artifactToProjectRef(a: Artifact) = { val m = a.getModuleRevisionId.getModuleId val tpe = a.getType ProjectRef(fixName(m.getName), m.getOrganisation, a.getExt, if (tpe != "jar") Some(tpe) else None) } - val nodes = report.getDependencies().asInstanceOf[_root_.java.util.List[IvyNode]].toSeq + val nodes = report.getDependencies().asInstanceOf[_root_.java.util.List[IvyNode]].asScala.toSeq val firstNode = nodes(0) val first = firstNode.getModuleId // the first element of "loaded", below, is the module that we have just resolved; in principle @@ -134,7 +134,7 @@ class IvyBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends Bu // this is transitive = false, only used to retrieve the jars that should be republished later val response = IvyMachinery.resolveIvy(newProjectConfig, baseDir, repos, log, transitive = false) val report = response.report - import scala.collection.JavaConversions._ + import scala.jdk.CollectionConverters.* def artifactToArtifactLocation(a: Artifact) = { val mr = a.getModuleRevisionId val m = mr.getModuleId @@ -145,7 +145,7 @@ class IvyBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends Bu ArtifactLocation(ProjectRef(trimName, m.getOrganisation, a.getExt, classifier), version /*mr.getRevision*/ , cross, pluginAttrs(mr)) } - val nodes = report.getDependencies().asInstanceOf[_root_.java.util.List[IvyNode]].toSeq + val nodes = report.getDependencies().asInstanceOf[_root_.java.util.List[IvyNode]].asScala.toSeq val firstNode = nodes(0) val publishArts = firstNode.getAllArtifacts.map(artifactToArtifactLocation).distinct @@ -157,7 +157,7 @@ class IvyBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends Bu val modulePluginInfo = pluginAttrs(module) val q = BuildArtifactsOut(Seq(BuildSubArtifactsOut("default-ivy-project", publishArts, - allPaths(localRepo).get.filterNot(file => file.isDirectory) map { LocalRepoHelper.makeArtifactSha(_, localRepo) }, + allPaths(localRepo).get().filterNot(file => file.isDirectory) map { LocalRepoHelper.makeArtifactSha(_, localRepo) }, com.typesafe.dbuild.manifest.ModuleInfo(organization = module.getOrganisation, name = fixName(module.getName), version = version, { import com.typesafe.dbuild.manifest.ModuleAttributes @@ -186,7 +186,7 @@ class IvyBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends Bu private def checkDependencies(project: RepeatableProjectBuild, baseDir: File, input: BuildInput, log: Logger): Seq[PublishIvyInfo] = { // I can run a check to verify that libraries that are cross-versioned (and therefore Scala-based) // have been made available via BuildArtifactsIn. If not, emit a message and possibly stop. - import scala.collection.JavaConversions._ + import scala.jdk.CollectionConverters.* val crossVersion = project.config.getCrossVersionHead val checkMissing = project.config.getCheckMissingHead val arts = input.artifacts.artifacts @@ -195,7 +195,7 @@ class IvyBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends Bu // to extract it here again, so that additional artifact-related details are available. val response = IvyMachinery.resolveIvy(project.config, baseDir, repos, log) val report = response.report - val nodes = report.getDependencies().asInstanceOf[_root_.java.util.List[IvyNode]].toSeq + val nodes = report.getDependencies().asInstanceOf[_root_.java.util.List[IvyNode]].asScala.toSeq val firstNode = nodes(0) val first = firstNode.getModuleId // We skip the first element (the artifacts resolved) and keep only the dependencies @@ -222,7 +222,7 @@ class IvyBuildSystem(repos: List[xsbti.Repository], workingDir: File) extends Bu if fixName(artifact.info.name) == fixName(a.getName) if artifact.info.extension == a.getExt } yield artifact).headOption - def printIvyDependency(someArt: Option[ArtifactLocation]) { + def printIvyDependency(someArt: Option[ArtifactLocation]): Unit = { log.info(" " + a.getModuleRevisionId.getOrganisation + "#" + a.getName + ";" + a.getModuleRevisionId.getRevision + (someArt map { art => " --> " + art.info.organization + "#" + art.info.name + art.crossSuffix + ";" + art.version diff --git a/support/src/main/scala/com/typesafe/dbuild/support/ivy/IvyMachinery.scala b/support/src/main/scala/com/typesafe/dbuild/support/ivy/IvyMachinery.scala index cc66378aa..46dc407dd 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/ivy/IvyMachinery.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/ivy/IvyMachinery.scala @@ -1,36 +1,36 @@ package com.typesafe.dbuild.support.ivy -import com.typesafe.dbuild.project.BuildSystem -import com.typesafe.dbuild.model._ -import java.io.File -import com.typesafe.dbuild.adapter.Adapter -import Adapter.FileRepository -import Adapter.Path._ -import Adapter.{IO,allPaths} -import Adapter.IO.relativize -import Adapter.syntaxio._ +import _root_.sbt.io.IO +import _root_.sbt.io.IO.relativize +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* +import _root_.sbt.librarymanagement.FileRepository +import com.typesafe.dbuild.adapter.Adapter.allPaths import com.typesafe.dbuild.logging.Logger -import sys.process._ -import com.typesafe.dbuild.repo.core.LocalRepoHelper +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.SeqStringH.* import com.typesafe.dbuild.model.Utils.readValue -import xsbti.Predefined._ +import com.typesafe.dbuild.project.BuildSystem +import com.typesafe.dbuild.project.build.BuildDirs.* +import com.typesafe.dbuild.repo.core.LocalRepoHelper +import com.typesafe.dbuild.support.sbt.Repositories.ivyPattern +import java.io.File import org.apache.ivy import ivy.Ivy -import ivy.plugins.resolver.{ BasicResolver, ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver } -import ivy.core.settings.IvySettings import ivy.core.module.descriptor.{ DefaultModuleDescriptor, DefaultDependencyDescriptor, Artifact } -import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter import ivy.core.module.id.{ ModuleId, ModuleRevisionId } -import ivy.core.resolve.{ ResolveEngine, ResolveOptions } import ivy.core.report.ResolveReport -import org.apache.ivy.core.retrieve.RetrieveOptions +import ivy.core.resolve.{ ResolveEngine, ResolveOptions } +import ivy.core.settings.IvySettings +import ivy.plugins.resolver.{ BasicResolver, ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver } import org.apache.ivy.core.deliver.DeliverOptions -import com.typesafe.dbuild.support.sbt.Repositories.ivyPattern -import org.apache.ivy.core.resolve.IvyNode import org.apache.ivy.core.module.descriptor.Configuration import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor -import com.typesafe.dbuild.project.build.BuildDirs._ -import com.typesafe.dbuild.model.SeqStringH._ +import org.apache.ivy.core.resolve.IvyNode +import org.apache.ivy.core.retrieve.RetrieveOptions +import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter +import sys.process.* +import xsbti.Predefined.* object IvyMachinery { // there are two stages to the madness below. The first: we create a dummy caller, and add the module we need as a dependency. @@ -52,7 +52,7 @@ object IvyMachinery { transitive: Boolean = true, useOptional: Boolean = true): ResolveResponse = { log.info("Running Ivy to extract project info: " + config.name) val extra = config.getExtra[IvyExtraConfig] - import extra._ + import extra.* // this is the one local to the project (extraction or build) val ivyHome = (baseDir / ".ivy2") val settings = new IvySettings() @@ -120,7 +120,7 @@ object IvyMachinery { val report: ResolveReport = theIvy.resolve(ivyFile.toURL(), resolveOptions) if (report.hasError) sys.error("Ivy resolution failure") - import scala.collection.JavaConversions._ + import scala.jdk.CollectionConverters.* val nodes = report.getDependencies().asInstanceOf[_root_.java.util.List[IvyNode]] if (nodes.isEmpty) { sys.error("Ivy error: no nodes after resolve. Please report.") @@ -132,7 +132,7 @@ object IvyMachinery { // diagnostic log.debug("Report:") - nodes foreach { n => + nodes.asScala foreach { n => log.debug("Node: " + n + (if (n.isLoaded) " (loaded)" else " (not loaded)")) log.debug("is optional: " + n.getConfigurations("optional").nonEmpty) if (n.isLoaded) { @@ -244,7 +244,7 @@ object IvyMachinery { deo.setConfs(md2.getConfigurations() map { _.getName() }) deo.setPubdate(new java.util.Date()) theIvy.deliver(modRevIdpublish, modRevIdpublish.getRevision, (ivyxmlDir / ivyPattern).getCanonicalPath, deo) - allPaths(ivyxmlDir).get.foreach { + allPaths(ivyxmlDir).get().foreach { f => if (f.getName == "ivy.xml") scala.io.Source.fromFile(f).getLines foreach { s => log.debug(s) } @@ -334,8 +334,8 @@ object IvyMachinery { } def hasImplicitClassifier(artifact: Artifact): Boolean = { - import collection.JavaConversions._ - artifact.getQualifiedExtraAttributes.keys.exists(_.asInstanceOf[String] startsWith "m:") + import scala.jdk.CollectionConverters.* + artifact.getQualifiedExtraAttributes.asScala.keys.exists(_.asInstanceOf[String] startsWith "m:") } def includeRepo(repo: xsbti.Repository) = !(isMavenLocal(repo)) @@ -348,7 +348,7 @@ object IvyMachinery { resolver } - def addResolvers(settings: IvySettings, ivyHome: File, repos: List[xsbti.Repository], dbuildRepoDir: File) { + def addResolvers(settings: IvySettings, ivyHome: File, repos: List[xsbti.Repository], dbuildRepoDir: File): Unit = { val newDefault = new ChainResolver { override def locate(artifact: Artifact) = if (hasImplicitClassifier(artifact)) null else super.locate(artifact) @@ -375,9 +375,9 @@ object IvyMachinery { } final class IvyLoggerInterface(logger: Logger) extends ivy.util.MessageLogger { - import SbtIvyLogger._ + import SbtIvyLogger.* def rawlog(msg: String, level: Int) = log(msg, level) - def log(msg: String, level: Int) { + def log(msg: String, level: Int): Unit = { import ivy.util.Message.{ MSG_DEBUG, MSG_VERBOSE, MSG_INFO, MSG_WARN, MSG_ERR } level match { case MSG_DEBUG => debug(msg) @@ -388,7 +388,7 @@ object IvyMachinery { } } - def debug(msg: String) {} + def debug(msg: String): Unit = {} def verbose(msg: String) = logger.verbose(msg) def deprecated(msg: String) = warn(msg) def info(msg: String) = logger.info(msg) @@ -396,7 +396,7 @@ object IvyMachinery { def warn(msg: String) = logger.warn(msg) def error(msg: String) = if (acceptError(msg)) logger.error(msg) - private def emptyList = java.util.Collections.emptyList[T forSome { type T }] + private def emptyList = java.util.Collections.emptyList[AnyRef] def getProblems = emptyList def getWarns = emptyList def getErrors = emptyList @@ -408,6 +408,6 @@ object IvyMachinery { def endProgress(msg: String) = info(msg) def isShowProgress = false - def setShowProgress(progress: Boolean) {} + def setShowProgress(progress: Boolean): Unit = {} } } diff --git a/support/src/main/scala/com/typesafe/dbuild/support/nil/ProjectResolver.scala b/support/src/main/scala/com/typesafe/dbuild/support/nil/ProjectResolver.scala index 70ebe35a1..15fbae802 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/nil/ProjectResolver.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/nil/ProjectResolver.scala @@ -1,13 +1,13 @@ package com.typesafe.dbuild.support.nil +import _root_.sbt.io.FileFilter.{ globFilter => toFF } +import _root_.sbt.io.IO +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* import com.typesafe.dbuild.logging.Logger -import com.typesafe.dbuild.model._ +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.project.resolve.ProjectResolver import java.io.File -import com.typesafe.dbuild.adapter.Adapter -import Adapter.{IO,toFF} -import Adapter.Path._ -import Adapter.syntaxio._ /** * The nil resolver does absolutely nothing. @@ -19,7 +19,7 @@ class NilProjectResolver() extends ProjectResolver { def resolve(config: ProjectBuildConfig, baseDir: File, log: Logger): ProjectBuildConfig = { // scrub the whole content before returning - IO.delete(baseDir.*(toFF("*")).get) + IO.delete(baseDir.*(toFF("*")).get()) config } } diff --git a/support/src/main/scala/com/typesafe/dbuild/support/sbt/DependencyExtractor.scala b/support/src/main/scala/com/typesafe/dbuild/support/sbt/DependencyExtractor.scala index 9667b312b..47f1417cb 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/sbt/DependencyExtractor.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/sbt/DependencyExtractor.scala @@ -1,27 +1,34 @@ package com.typesafe.dbuild.support.sbt -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.logging.Logger -import com.typesafe.dbuild.adapter.Adapter -import Adapter.{ IO, Path } -import Adapter.syntaxio._ -import Path._ import _root_.java.io.File -import sys.process.Process -import com.typesafe.dbuild.model.Utils.{ readValue, writeValue } +import _root_.sbt.io.syntax.* +import _root_.sbt.io.{ IO, Path } +import com.typesafe.dbuild.logging.Logger import com.typesafe.dbuild.logging.Logger.logFullStackTrace -import com.fasterxml.jackson.annotation.JsonProperty -import com.typesafe.dbuild.model.SeqStringH._ +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.CirceSupport.* +import com.typesafe.dbuild.model.SeqStringH.* +import com.typesafe.dbuild.model.Utils.{ readValue, writeValue } import com.typesafe.dbuild.utils.TrackedProcessBuilder +import io.circe.generic.semiauto.{ deriveEncoder, deriveDecoder } +import io.circe.{ Encoder, Decoder } +import sys.process.Process +import Path.* /** * Input data to the dbuild sbt plugin */ case class ExtractionInput( projects: Seq[String], - @JsonProperty("excluded-projects") excludedProjects: Seq[String], + excludedProjects: Seq[String], extractionScalaVersion: Option[String], debug: Boolean) +object ExtractionInput { + implicit val extractionInputEncoder: Encoder[ExtractionInput] = + dropNullValues(renamedEnc("excludedProjects" -> "excluded-projects")(deriveEncoder[ExtractionInput])) + implicit val extractionInputDecoder: Decoder[ExtractionInput] = + renamedDec("excludedProjects" -> "excluded-projects")(deriveDecoder[ExtractionInput]) +} object SbtExtractor { @@ -76,7 +83,7 @@ object SbtExtractor { // Let's place them in the required dirs SbtRunner.writeSbtFiles(projectDir, finalSbtFiles, log, debug) - import SbtRunner.SbtFileNames._ + import SbtRunner.SbtFileNames.* // This is for the first level only val inputDataFirst = ExtractionInput(extra.projects, extra.exclude, scalaVersion, debug) val inputDataAll = inputDataFirst +: Stream.fill(levels - 1)(ExtractionInput(Seq.empty, Seq.empty, None, debug)) diff --git a/support/src/main/scala/com/typesafe/dbuild/support/sbt/Repositories.scala b/support/src/main/scala/com/typesafe/dbuild/support/sbt/Repositories.scala index 477e9eac2..1d04fcf01 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/sbt/Repositories.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/sbt/Repositories.scala @@ -1,9 +1,9 @@ package com.typesafe.dbuild.support.sbt -import com.typesafe.dbuild.model._ import _root_.java.io.File import _root_.java.net.{ URI, URL } -import com.typesafe.dbuild.adapter.Adapter.IO +import _root_.sbt.io.IO +import com.typesafe.dbuild.model.* object Repositories { val ivyPattern = "[organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]" diff --git a/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtBuildRunner.scala b/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtBuildRunner.scala index e4027c0a7..8289583a5 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtBuildRunner.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtBuildRunner.scala @@ -1,21 +1,23 @@ package com.typesafe.dbuild.support.sbt -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.logging.Logger -import com.typesafe.dbuild.adapter.Adapter -import Adapter.{ IO, Path, Logger => SbtLogger } -import Adapter.syntaxio._ -import Path._ import _root_.java.io.File -import sys.process.Process -import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } +import _root_.sbt.io.syntax.* +import _root_.sbt.io.{ IO, Path } +import _root_.sbt.util.{ Logger => SbtLogger } +import com.typesafe.dbuild.logging.Logger import com.typesafe.dbuild.logging.Logger.logFullStackTrace -import com.typesafe.dbuild.project.build.BuildDirs._ -import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames._ -import com.typesafe.dbuild.support.sbt.SbtRunner.{ sbtIvyCache, buildArtsFile } +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.model.SeqSeqString -import com.typesafe.dbuild.model.SeqStringH._ +import com.typesafe.dbuild.model.SeqStringH.* +import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } +import com.typesafe.dbuild.project.build.BuildDirs.* +import com.typesafe.dbuild.support.sbt.SbtRunner.SbtFileNames.* +import com.typesafe.dbuild.support.sbt.SbtRunner.{ sbtIvyCache, buildArtsFile } import com.typesafe.dbuild.utils.TrackedProcessBuilder +import io.circe.generic.semiauto.{ deriveEncoder, deriveDecoder } +import io.circe.{ Encoder, Decoder } +import sys.process.Process +import Path.* /** * Rewiring a level needs the information contained in RewireInput: @@ -30,11 +32,23 @@ import com.typesafe.dbuild.utils.TrackedProcessBuilder */ case class RewireInput(in: BuildArtifactsIn, subproj: Seq[String], crossVersion: String, checkMissing: Boolean, rewriteOverrides: Boolean, debug: Boolean) +object RewireInput { + implicit val rewireInputEncoder: Encoder[RewireInput] = deriveEncoder[RewireInput] + implicit val rewireInputDecoder: Decoder[RewireInput] = deriveDecoder[RewireInput] +} /** * Input to generateArtifacts() */ case class TestParams(runTests: Boolean, skipMissingTests: Boolean, testTasks: Seq[String]) +object TestParams { + implicit val testParamsEncoder: Encoder[TestParams] = deriveEncoder[TestParams] + implicit val testParamsDecoder: Decoder[TestParams] = deriveDecoder[TestParams] +} case class GenerateArtifactsInput(info: BuildInput, testParams: TestParams, debug: Boolean) +object GenerateArtifactsInput { + implicit val generateArtifactsInputEncoder: Encoder[GenerateArtifactsInput] = deriveEncoder[GenerateArtifactsInput] + implicit val generateArtifactsInputDecoder: Decoder[GenerateArtifactsInput] = deriveDecoder[GenerateArtifactsInput] +} object SbtBuilder { diff --git a/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtBuildSystem.scala b/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtBuildSystem.scala index 85e4b60ac..3543adf28 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtBuildSystem.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtBuildSystem.scala @@ -1,21 +1,21 @@ package com.typesafe.dbuild.support.sbt -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.support.BuildSystemCore -import com.typesafe.dbuild.adapter.Adapter.Path._ -import com.typesafe.dbuild.logging.Logger -import com.typesafe.dbuild.model.SbtExtraConfig import _root_.java.io.File +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* import com.typesafe.dbuild.adapter.Defaults -import com.typesafe.dbuild.adapter.Adapter.syntaxio._ -import com.typesafe.dbuild.repo.core.GlobalDirs -import com.typesafe.dbuild.project.dependencies.Extractor +import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.SbtExtraConfig +import com.typesafe.dbuild.model.SeqSeqStringH.* +import com.typesafe.dbuild.model.SeqStringH.* +import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } import com.typesafe.dbuild.project.build.LocalBuildRunner +import com.typesafe.dbuild.project.dependencies.Extractor import com.typesafe.dbuild.project.{ BuildSystem, BuildData } -import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } +import com.typesafe.dbuild.repo.core.GlobalDirs +import com.typesafe.dbuild.support.BuildSystemCore import com.typesafe.dbuild.support.sbt.SbtRunner.{ sbtIvyCache, buildArtsFile } -import com.typesafe.dbuild.model.SeqSeqStringH._ -import com.typesafe.dbuild.model.SeqStringH._ import com.typesafe.dbuild.utils.TrackedProcessBuilder /** Implementation of the SBT build system. */ @@ -93,7 +93,7 @@ class SbtBuildSystem(repos: List[xsbti.Repository], workingDir: File, debug: Boo val config = SbtBuildConfig(ec, project.config.crossVersion getOrElse sys.error("Internal error: crossVersion not expanded in runBuild."), project.config.checkMissing getOrElse sys.error("Internal error: checkMissing not expanded in runBuild."), project.config.rewriteOverrides getOrElse sys.error("Internal error: rewriteOverrides not expanded in runBuild."), info) - SbtBuilder.buildSbtProject(repos, runner)(projDir, config, tracker, buildData.log, buildData.debug) + SbtBuilder.buildSbtProject(repos, runner)(projDir, config, tracker, buildData.log, buildData.debug)() readValue[BuildArtifactsOut](buildArtsFile(projDir)) } } diff --git a/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtRunner.scala b/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtRunner.scala index 3fb948d37..a91c704c9 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtRunner.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/sbt/SbtRunner.scala @@ -1,18 +1,20 @@ package com.typesafe.dbuild.support.sbt import _root_.java.io.File -import com.typesafe.dbuild.adapter.{Adapter,Defaults} -import Adapter.{ IO, Path, toFF, Logger => SbtLogger } -import Adapter.syntaxio._ -import Path._ +import _root_.sbt.io.FileFilter.{ globFilter => toFF } +import _root_.sbt.io.syntax.* +import _root_.sbt.io.{ IO, Path } +import _root_.sbt.util.{ Logger => SbtLogger } +import com.typesafe.dbuild.adapter.Defaults import com.typesafe.dbuild.logging.Logger -import sys.process._ +import com.typesafe.dbuild.logging.Logger.logFullStackTrace import com.typesafe.dbuild.model.ExtraConfig -import org.apache.commons.io.FileUtils.readFileToString import com.typesafe.dbuild.model.Utils.{ readValue, writeValue } -import org.apache.commons.io.FileUtils.writeStringToFile -import com.typesafe.dbuild.logging.Logger.logFullStackTrace import com.typesafe.dbuild.utils.TrackedProcessBuilder +import org.apache.commons.io.FileUtils.readFileToString +import org.apache.commons.io.FileUtils.writeStringToFile +import sys.process.* +import Path.* /** * A runner for SBT. @@ -22,9 +24,11 @@ class SbtRunner(repos: List[xsbti.Repository], globalBase: File, debug: Boolean) private val launcherJar = SbtRunner.initSbtGlobalBase(globalBase, debug) private val defaultProps = - Map("sbt.global.base" -> globalBase.getAbsolutePath, - "sbt.override.build.repos" -> "true", - "sbt.log.noformat" -> "true") + Map( + "sbt.global.base" -> globalBase.getAbsolutePath, + "sbt.coursier.home" -> (globalBase / "coursier").getAbsolutePath, + "sbt.log.noformat" -> "true", + ) def localIvyProps: Map[String, String] = Map("sbt.ivy.home" -> (globalBase / ".ivy2").getAbsolutePath) @@ -66,7 +70,8 @@ class SbtRunner(repos: List[xsbti.Repository], globalBase: File, debug: Boolean) IO.withTemporaryFile("sbtrunner", "lastExceptionMessage") { lastMsg => val cmd = SbtRunner.makeShell( launcherJar.getAbsolutePath, - defaultProps + ("dbuild.sbt-runner.last-msg" -> lastMsg.getCanonicalPath, + defaultProps + ("sbt.override.build.repos" -> "true", + "dbuild.sbt-runner.last-msg" -> lastMsg.getCanonicalPath, "sbt.version" -> useSbtVersion), javaProps, extraArgs)(args: _*) @@ -112,7 +117,7 @@ class SbtRunner(repos: List[xsbti.Repository], globalBase: File, debug: Boolean) val lines = scala.io.Source.fromFile(buildProps).getLines val regex = " *sbt.version *= *([^ ]*) *".r val sbtVer = (for { - regex(v) <- lines + case regex(v) <- lines } yield v).toList.headOption sbtVer } else None @@ -142,7 +147,7 @@ object SbtRunner { } private def makeArgsFromProps(props: Map[String, String]): Seq[String] = - props.map { case (k, v) => "-D%s=%s" format (k, v) } { collection.breakOut } + props.map { case (k, v) => "-D%s=%s" format (k, v) }.toSeq def makeShell(launcherJar: String, defaultProps: Map[String, String], @@ -177,7 +182,7 @@ object SbtRunner { def buildLevels(dir: File): Int = { val sub = dir / "project" val subSub = sub / "project" - if (sub.isDirectory && (sub.*(toFF("*.sbt")).get.nonEmpty || (subSub.isDirectory() && sub.*(toFF("*.scala")).get.nonEmpty))) + if (sub.isDirectory && (sub.*(toFF("*.sbt")).get().nonEmpty || (subSub.isDirectory() && sub.*(toFF("*.scala")).get().nonEmpty))) buildLevels(sub) + 1 else 1 } @@ -236,7 +241,7 @@ object SbtRunner { val repositoriesFileName = "repositories" } - import SbtFileNames._ + import SbtFileNames.* ///////////////////////////////////////////////////// // // Below, utilities used by SbtExtractor and SbtBuilder @@ -284,7 +289,7 @@ object SbtRunner { * in dir/.dbuild, the second in dir/project/.dbuild, the dir/project/project/.dbuild, * and so on. */ - def placeInputFiles[T](mainDir: File, fileName: String, data: Seq[T], log: SbtLogger, debug: Boolean)(implicit m: Manifest[T]) = + def placeInputFiles[T](mainDir: File, fileName: String, data: Seq[T], log: SbtLogger, debug: Boolean)(implicit e: io.circe.Encoder[T]) = placeFiles(mainDir, data.map { writeValue(_) }, fileName, Some(dbuildSbtDirName), s => if (debug) log.debug("Placing one input file in " + s)) def rewireInputFile(dir: File) = dir / dbuildSbtDirName / rewireInputFileName @@ -301,7 +306,7 @@ object SbtRunner { /** * Collect the output files from the various dirs, and return them as a sequence. */ - def collectOutputFiles[T](mainDir: File, fileName: String, levels: Int, log: Logger, debug: Boolean)(implicit m: Manifest[T]): Seq[T] = { + def collectOutputFiles[T](mainDir: File, fileName: String, levels: Int, log: Logger, debug: Boolean)(implicit d: io.circe.Decoder[T]): Seq[T] = { def scan(left: Int, dir: File): Seq[T] = { if (left > 0) { val file = dir / dbuildSbtDirName / fileName @@ -325,18 +330,18 @@ object SbtRunner { * If quiet, then silence Ivy resolution */ def ivyQuiet(debug: Boolean) = - if (debug) "ivyLoggingLevel in Global := UpdateLogging.Full\n\n" else "ivyLoggingLevel in Global := UpdateLogging.Quiet\n\n" + if (debug) "Global / ivyLoggingLevel := UpdateLogging.Full\n\n" else "Global / ivyLoggingLevel := UpdateLogging.Quiet\n\n" /** * The string needed to load the dbuild plugin */ val addDBuildPlugin = - """dependencyOverrides in ThisBuild += "org.scala-lang.modules" %% "scala-xml" % "2.1.0"""" + "\n\n" + + """ThisBuild / dependencyOverrides += "org.scala-lang.modules" %% "scala-xml" % "2.1.0"""" + "\n\n" + """addSbtPlugin("com.typesafe.dbuild" % "plugin" % """ + '"' + Defaults.version + "\")\n\n" /** Perform a state transformation using onLoad() */ def onLoad(activity: String) = { - "onLoad in Global ~= (previousOnLoad => previousOnLoad andThen (state => { " + activity + " }))\n\n" + "Global / onLoad ~= (previousOnLoad => previousOnLoad andThen (state => { " + activity + " }))\n\n" } // stuff related to generateArtifacts() diff --git a/support/src/main/scala/com/typesafe/dbuild/support/scala/ScalaBuildSystem.scala b/support/src/main/scala/com/typesafe/dbuild/support/scala/ScalaBuildSystem.scala index 6d2d6a951..230205f85 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/scala/ScalaBuildSystem.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/scala/ScalaBuildSystem.scala @@ -1,31 +1,32 @@ package com.typesafe.dbuild.support.scala -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.support.BuildSystemCore -import com.typesafe.dbuild.utils.TrackedProcessBuilder -import org.apache.commons.io.FilenameUtils -import org.apache.commons.io.FileUtils import _root_.java.io.File -import com.typesafe.dbuild.adapter.Adapter -import Adapter.Path._ -import Adapter.{IO,NameFilter,allPaths,toFF} -import Adapter.IO.relativize -import Adapter.syntaxio._ +import _root_.sbt.io.FileFilter.{ globFilter => toFF } +import _root_.sbt.io.IO.relativize +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* +import _root_.sbt.io.{ IO, NameFilter } +import collection.JavaConverters.* +import com.typesafe.dbuild.adapter.Adapter.allPaths import com.typesafe.dbuild.logging.Logger -import sys.process._ -import com.typesafe.dbuild.repo.core.LocalRepoHelper +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.model.SeqStringH.* import com.typesafe.dbuild.model.Utils.{ writeValue, readValue } -import com.typesafe.dbuild.project.dependencies.Extractor +import com.typesafe.dbuild.project.build.BuildDirs.dbuildDirName import com.typesafe.dbuild.project.build.LocalBuildRunner +import com.typesafe.dbuild.project.dependencies.Extractor import com.typesafe.dbuild.project.{ BuildSystem, BuildData } -import collection.JavaConverters._ -import org.apache.maven.model.{ Model, Dependency } -import org.apache.maven.model.io.xpp3.{ MavenXpp3Reader, MavenXpp3Writer } -import org.apache.maven.model.Dependency -import org.apache.ivy.util.ChecksumHelper +import com.typesafe.dbuild.repo.core.LocalRepoHelper +import com.typesafe.dbuild.support.BuildSystemCore import com.typesafe.dbuild.support.NameFixer.fixName -import com.typesafe.dbuild.project.build.BuildDirs.dbuildDirName -import com.typesafe.dbuild.model.SeqStringH._ +import com.typesafe.dbuild.utils.TrackedProcessBuilder +import org.apache.commons.io.FileUtils +import org.apache.commons.io.FilenameUtils +import org.apache.ivy.util.ChecksumHelper +import org.apache.maven.model.Dependency +import org.apache.maven.model.io.xpp3.{ MavenXpp3Reader, MavenXpp3Writer } +import org.apache.maven.model.{ Model, Dependency } +import sys.process.* /** Implementation of the Scala build system. */ object ScalaBuildSystem extends BuildSystemCore { @@ -125,9 +126,9 @@ object ScalaBuildSystem extends BuildSystemCore { name: String, org: String): Option[String] = getVersion(findArtifact(arts, name, org)) - val debugOptions = if (buildData.debug) Seq("-debug") else Seq.empty + val debugOptions: Seq[String] = if (buildData.debug) Seq("-debug") else Seq.empty - val rewireOptions = if (hasPublishLocal) { + val rewireOptions: Seq[String] = if (hasPublishLocal) { val customScalaVersion = findVersion(input.artifacts.artifacts, "org.scala-lang", "scala-library") // "starr.version" currently also applies to scala-compiler and scala-reflect @@ -174,7 +175,7 @@ object ScalaBuildSystem extends BuildSystemCore { ("com.typesafe.akka", "akka-actor", "akka-actor"), ("org.scala-lang", "scala-actors-migration", "actors-migration")) - val extraRewireOptions = (for { + val extraRewireOptions: Seq[String] = (for { (org, name, prop) <- moduleData art <- findArtifact(input.artifacts.artifacts, org, name) } yield { @@ -207,13 +208,14 @@ object ScalaBuildSystem extends BuildSystemCore { targets foreach { case (target, path) => // erase the content of the private cache before each stage of a build, just in case. - IO.delete(localM2repo.*(toFF("*")).get) + IO.delete(localM2repo.*(toFF("*")).get()) val targetDir = path.split("/").foldLeft(dir)(_ / _) - tracker.!(Process(Seq("ant", target, + val antArgs: Seq[String] = Seq("ant", target, "-Dlocal.snapshot.repository=" + localRepo.getAbsolutePath, "-Dlocal.release.repository=" + localRepo.getAbsolutePath, "-Dmaven.version.number=" + version) ++ rewireOptions ++ - debugOptions ++ ec.buildOptions, Some(targetDir), + debugOptions ++ ec.buildOptions.s + tracker.!(Process(antArgs, Some(targetDir), // The special and rather unsupported env variable "_JAVA_OPTIONS" is the *only* thing // capable to forcibly change user.home in all the jvm invocations therein. Changing only // HOME or defining -Duser.home when calling ant, will *still* cause ~/.m2 to be used by ant & maven @@ -238,7 +240,7 @@ object ScalaBuildSystem extends BuildSystemCore { // org-with-dots/name/var/name-ver.pom // org-with-dots/name_suff/var/name_suff-ver.pom val nameFilter = (toFF(name)) | (toFF((name + "_*"))) - val potentialDirs = od.*(nameFilter).get + val potentialDirs = od.*(nameFilter).get() if (potentialDirs.isEmpty) { sys.error("Cannot find artifacts dir for: " + modID) } @@ -249,7 +251,7 @@ object ScalaBuildSystem extends BuildSystemCore { // let's look for var/nameWithCross-ver.pom val SearchPattern = """([^/]*)/([^/]*)-\1.pom""".r val crossSuffixesAndVers = potentialDirs.flatMap { d => - allPaths(d).get.filterNot(file => file.isDirectory).map { f => + allPaths(d).get().filterNot(file => file.isDirectory).map { f => val relative = relativize(d, f) getOrElse sys.error("Internal error in relative paths creation. Please report.") relative match { case SearchPattern(ver, nameAndCross) => @@ -281,7 +283,7 @@ object ScalaBuildSystem extends BuildSystemCore { // use the list of artifacts as a hint as to which directories should be looked up, // but actually scan the dirs rather than using the list of artifacts (there may be // additional files like checksums, for instance). - artifacts.map(artifactDir(localRepo, _, crossSuffix)).distinct.flatMap { allPaths(_).get }. + artifacts.map(artifactDir(localRepo, _, crossSuffix)).distinct.flatMap { allPaths(_).get() }. filterNot(file => file.isDirectory || file.getName == "maven-metadata-local.xml").map(f) } @@ -377,7 +379,7 @@ object ScalaBuildSystem extends BuildSystemCore { } def antHasTarget(target: String, dir: File) = - Process("ant -p", dir).lines.exists(_.startsWith(" " + target + " ")) + Process("ant -p", dir).lineStream.exists(_.startsWith(" " + target + " ")) /** Read version from build.number but fake the rest of the ExtractedBuildMeta.*/ private def fallbackMeta(baseDir: File): ExtractedBuildMeta = { diff --git a/support/src/main/scala/com/typesafe/dbuild/support/svn/ProjectResolver.scala b/support/src/main/scala/com/typesafe/dbuild/support/svn/ProjectResolver.scala index f03a369af..fa9af6f16 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/svn/ProjectResolver.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/svn/ProjectResolver.scala @@ -1,11 +1,11 @@ package com.typesafe.dbuild.support.svn -import com.typesafe.dbuild.adapter.Adapter -import Adapter.Path._ -import Adapter.{IO,toFF} -import Adapter.syntaxio._ -import com.typesafe.dbuild.model._ +import _root_.sbt.io.FileFilter.{ globFilter => toFF } +import _root_.sbt.io.IO +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.project.resolve.ProjectResolver import com.typesafe.dbuild.support.UriUtil @@ -25,7 +25,7 @@ class SvnProjectResolver extends ProjectResolver { val uri = new _root_.java.net.URI(config.uri) // clean the directory content, just in case there are leftovers - IO.delete(dir.*(toFF("*")).get) + IO.delete(dir.*(toFF("*")).get()) if(!(dir / ".svn" ).exists) Svn.checkout(uri, dir, log) else Svn.revert(dir, log) diff --git a/support/src/main/scala/com/typesafe/dbuild/support/svn/Svn.scala b/support/src/main/scala/com/typesafe/dbuild/support/svn/Svn.scala index c550b55e3..672cb9988 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/svn/Svn.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/svn/Svn.scala @@ -1,12 +1,12 @@ package com.typesafe.dbuild.support.svn -import sys.process._ import _root_.java.io.File import _root_.java.net.URI -import com.typesafe.dbuild.adapter.Adapter.IO +import _root_.sbt.io.IO import com.typesafe.dbuild.logging.Logger +import com.typesafe.dbuild.support.OS.* import com.typesafe.dbuild.support.UriUtil -import com.typesafe.dbuild.support.OS._ +import sys.process.* /** A svn runner. This class exists solely for Scalatest. */ object Svn { @@ -16,7 +16,7 @@ object Svn { object HasRevision { def unapply(in: String): Option[String] = (for { - Revision(rev) <- (in split "[\r\n]+").view + case Revision(rev) <- (in split "[\r\n]+").view } yield rev).headOption } diff --git a/support/src/main/scala/com/typesafe/dbuild/support/test/ProjectResolver.scala b/support/src/main/scala/com/typesafe/dbuild/support/test/ProjectResolver.scala index 942799bf6..b330ee894 100644 --- a/support/src/main/scala/com/typesafe/dbuild/support/test/ProjectResolver.scala +++ b/support/src/main/scala/com/typesafe/dbuild/support/test/ProjectResolver.scala @@ -1,14 +1,14 @@ package com.typesafe.dbuild.support.test +import _root_.java.net.URI +import _root_.sbt.io.FileFilter.{ globFilter => toFF } +import _root_.sbt.io.IO +import _root_.sbt.io.Path.* +import _root_.sbt.io.syntax.* import com.typesafe.dbuild.logging.Logger -import com.typesafe.dbuild.model._ +import com.typesafe.dbuild.model.* import com.typesafe.dbuild.project.resolve.ProjectResolver import java.io.File -import _root_.java.net.URI -import com.typesafe.dbuild.adapter.Adapter -import Adapter.{IO,toFF} -import Adapter.Path._ -import Adapter.syntaxio._ /** * The test resolver does absolutely nothing; however, it fails every now and then (for testing). @@ -20,7 +20,7 @@ class TestProjectResolver() extends ProjectResolver { def resolve(config: ProjectBuildConfig, baseDir: File, log: Logger): ProjectBuildConfig = { // scrub the whole content before returning - IO.delete(baseDir.*(toFF("*")).get) + IO.delete(baseDir.*(toFF("*")).get()) val rand = new java.util.Random // abort resolution 10% of the times diff --git a/supportGit/src/main/scala/com/typesafe/dbuild/support/git/Git.scala b/supportGit/src/main/scala/com/typesafe/dbuild/support/git/Git.scala index d21671454..da06d339a 100644 --- a/supportGit/src/main/scala/com/typesafe/dbuild/support/git/Git.scala +++ b/supportGit/src/main/scala/com/typesafe/dbuild/support/git/Git.scala @@ -1,28 +1,28 @@ package com.typesafe.dbuild.support.git -import sys.process._ import _root_.java.io.File import _root_.java.net.URI +import collection.JavaConverters.* import com.typesafe.dbuild.logging.Logger -import org.eclipse.jgit.api.{ Git => JGit, _ } -import org.eclipse.jgit.storage.file._ -import org.eclipse.jgit.transport._ -import org.eclipse.jgit.transport.TagOpt._ +import com.typesafe.dbuild.support.OS +import com.typesafe.dbuild.utils.Time.timed +import java.io.CharArrayWriter +import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode.* +import org.eclipse.jgit.api.ListBranchCommand.ListMode.* +import org.eclipse.jgit.api.ResetCommand.ResetType.HARD +import org.eclipse.jgit.api.{ Git => JGit, * } +import org.eclipse.jgit.lib.Constants.HEAD +import org.eclipse.jgit.lib.NullProgressMonitor +import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.ProgressMonitor import org.eclipse.jgit.lib.TextProgressMonitor -import org.eclipse.jgit.lib.NullProgressMonitor import org.eclipse.jgit.lib.ThreadSafeProgressMonitor -import collection.JavaConverters._ -import java.io.CharArrayWriter -import org.eclipse.jgit.api.ListBranchCommand.ListMode._ -import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode._ import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevWalk -import org.eclipse.jgit.lib.ObjectId -import org.eclipse.jgit.api.ResetCommand.ResetType.HARD -import org.eclipse.jgit.lib.Constants.HEAD -import com.typesafe.dbuild.utils.Time.timed -import com.typesafe.dbuild.support.OS +import org.eclipse.jgit.storage.file.* +import org.eclipse.jgit.transport.* +import org.eclipse.jgit.transport.TagOpt.* +import sys.process.* sealed abstract class GitImplementation { /** a logical descriptor of the repository */ @@ -86,7 +86,7 @@ object GitJGit extends GitImplementation { // optional output logging or monitoring // no implicit classes yet, we are still on 2.9 type T[A, B <: GitCommand[A]] = GitCommand[A] { def setProgressMonitor(monitor: ProgressMonitor): B } - implicit def asRunnableCommand[A, B <: GitCommand[A]](t: T[A, B]) = new RunnableCommand(t) + implicit def asRunnableCommand[A, B <: GitCommand[A]](t: T[A, B]): RunnableCommand[A, B] = new RunnableCommand(t) class RunnableCommand[A, B <: GitCommand[A]](c: T[A, B]) { def run(log: Logger) = { // a TextProgressMonitor() accepts a java.io.Writer. However, we need diff --git a/supportGit/src/main/scala/com/typesafe/dbuild/support/git/ProjectResolver.scala b/supportGit/src/main/scala/com/typesafe/dbuild/support/git/ProjectResolver.scala index d97ba17a5..8f9559c0f 100644 --- a/supportGit/src/main/scala/com/typesafe/dbuild/support/git/ProjectResolver.scala +++ b/supportGit/src/main/scala/com/typesafe/dbuild/support/git/ProjectResolver.scala @@ -1,13 +1,13 @@ package com.typesafe.dbuild.support.git -import sbt.io.Path._ -import sbt.io.syntax._ -import com.typesafe.dbuild.model._ -import com.typesafe.dbuild.project.resolve.ProjectResolver +import com.typesafe.dbuild.hashing import com.typesafe.dbuild.logging.Logger import com.typesafe.dbuild.logging.Logger.prepareLogMsg +import com.typesafe.dbuild.model.* +import com.typesafe.dbuild.project.resolve.ProjectResolver import com.typesafe.dbuild.support.UriUtil -import com.typesafe.dbuild.hashing +import sbt.io.Path.* +import sbt.io.syntax.* /** * This class knows how to resolve Git projects and @@ -33,7 +33,7 @@ class GitProjectResolver extends ProjectResolver { val uri = new _root_.java.net.URI(config.uri) val uriString = UriUtil.dropFragment(uri).toASCIIString - val baseName = ({s:String => if (s.endsWith(".git")) s.dropRight(4) else s})(uri.getRawPath().split("/").last) + val baseName = ({(s:String) => if (s.endsWith(".git")) s.dropRight(4) else s})(uri.getRawPath().split("/").last) val cloneDir = com.typesafe.dbuild.repo.core.GlobalDirs.clonesDir / ((hashing sha1 uriString) + "-" + baseName) val ref = Option(uri.getFragment()) getOrElse "master" From 3e28b8ea0b39c45529b710b25746630397896720 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 27 Aug 2026 22:00:11 -0400 Subject: [PATCH 2/2] Update packaging to use sbt-launch --- build.sbt | 2 +- dist/src/universal/bin/dbuild | 2 +- dist/src/universal/bin/drepo | 2 +- project/DbuildLauncher.scala | 9 --------- project/packaging.scala | 17 +++-------------- 5 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 project/DbuildLauncher.scala diff --git a/build.sbt b/build.sbt index c9bab34a4..d30c5997f 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import Dependencies._ import RemoteDepHelper._ import SbtSupport._ -def MyVersion: String = "0.9.20" +def MyVersion: String = "0.10.0-SNAPSHOT" ThisBuild / scalaVersion := scala3 diff --git a/dist/src/universal/bin/dbuild b/dist/src/universal/bin/dbuild index a5b4bcfec..edeb9b47d 100644 --- a/dist/src/universal/bin/dbuild +++ b/dist/src/universal/bin/dbuild @@ -18,5 +18,5 @@ declare -r SCRIPT_DIR="$(dirname $SCRIPT_PATH)" java -Xss16M \ -Dsbt.boot.properties=$SCRIPT_DIR/dbuild.properties \ -Djline.shutdownhook=true \ - -jar $SCRIPT_DIR/dbuild-launcher.jar \ + -jar $SCRIPT_DIR/sbt-launcher.jar \ $@ diff --git a/dist/src/universal/bin/drepo b/dist/src/universal/bin/drepo index adb441310..b3e7d33ff 100644 --- a/dist/src/universal/bin/drepo +++ b/dist/src/universal/bin/drepo @@ -5,5 +5,5 @@ SCRIPT_DIR=$(dirname $0) java -Xss16M \ -Dsbt.boot.properties=$SCRIPT_DIR/drepo.properties \ -Djline.shutdownhook=true \ - -jar $SCRIPT_DIR/dbuild-launcher.jar \ + -jar $SCRIPT_DIR/sbt-launcher.jar \ $@ diff --git a/project/DbuildLauncher.scala b/project/DbuildLauncher.scala deleted file mode 100644 index dd37bc06a..000000000 --- a/project/DbuildLauncher.scala +++ /dev/null @@ -1,9 +0,0 @@ -import sbt._ - -object DbuildLauncher { - val launcherVersion = "1.0.1-d1" - val launcher = "com.typesafe.dbuild" % "launcher" % launcherVersion - val uri = "https://dl.cloudsmith.io/public/lightbend/maven-releases/maven/com/typesafe/dbuild/launcher/" + launcherVersion + - "/launcher-" + launcherVersion+".jar" -} - diff --git a/project/packaging.scala b/project/packaging.scala index 1fe8e641c..b4b7f52aa 100644 --- a/project/packaging.scala +++ b/project/packaging.scala @@ -1,4 +1,3 @@ -import DbuildLauncher.{launcherVersion,uri} import com.typesafe.sbt.SbtNativePackager.* import com.typesafe.sbt.packager.Keys.* import sbt.* @@ -60,20 +59,10 @@ object Packaging { Seq((Universal / mappings) ++= Seq( Packaging.makeDBuildProps(target.value, sourceDirectory.value, ((build / scalaVersion)).value, ((build / version)).value), Packaging.makeDRepoProps(target.value, sourceDirectory.value, ((build / scalaVersion)).value, ((build / version)).value), - Packaging.dbuildLauncher(target.value, streams.value.log, ((build / version)).value) + SbtSupport.sbtLaunchJar.value.head -> "bin/sbt-launcher.jar", ) ) - def dbuildLauncher(target: File, log: Logger, dbuildVersion: String) = { - val tdir = target / "dbuild-launcher" - if(!tdir.exists) tdir.mkdirs() - val file = tdir / "dbuild-launcher.jar" - log.info("Downloading dbuild launcher "+ uri +" to "+ file.getAbsolutePath() +"...") - import scala.sys.process.* - url(uri).#>(file).! - file -> "bin/dbuild-launcher.jar" - } - def makeDRepoProps(t: File, src: File, sv: String, v: String): (File, String) = makeProps(t,src,sv,v,"repo","com.typesafe.dbuild.repo.core.SbtRepoMain") def makeDBuildProps(t: File, src: File, sv: String, v: String): (File, String) = makeProps(t,src,sv,v,"build","com.typesafe.dbuild.build.SbtBuildMain") @@ -88,10 +77,10 @@ object Packaging { [app] org: com.typesafe.dbuild - name: %s + name: %s_3 version: %s class: %s - cross-versioned: binary + cross-versioned: false components: xsbti [repositories]