From 5398f6e1aa3d1e60633d2aef61684267bb12359e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 18 Jun 2023 15:48:18 -0400 Subject: [PATCH] Make sbt new extensible Problem ------- `sbt new` (`sbt init`) hardcodes the templates, which I think is ok, but without changing much, we can make it extensible. Solution -------- This adds two new keys `templateDescriptions` and `templateRunLocal`, which can customize the behavior for in-house usage etc. --- main/src/main/scala/sbt/Defaults.scala | 15 +++++++++++++++ main/src/main/scala/sbt/Keys.scala | 2 ++ main/src/main/scala/sbt/TemplateCommandUtil.scala | 14 +++++++++----- .../main/scala/sbt/internal/TaskProgress.scala | 4 +++- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 4c3801d30..0ea596dcc 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -361,6 +361,8 @@ object Defaults extends BuildCommon { fork :== false, initialize :== {}, templateResolverInfos :== Nil, + templateDescriptions :== TemplateCommandUtil.defaultTemplateDescriptions, + templateRunLocal := templateRunLocalInputTask(runLocalTemplate).evaluated, forcegc :== sys.props .get("sbt.task.forcegc") .map(java.lang.Boolean.parseBoolean) @@ -2644,6 +2646,19 @@ object Defaults extends BuildCommon { if (useCoursier.value) CoursierDependencyResolution(csrConfiguration.value) else IvyDependencyResolution(ivyConfiguration.value) } + + def templateRunLocalInputTask( + runLocal: (Seq[String], Logger) => Unit + ): Initialize[InputTask[Unit]] = + Def.inputTask { + import Def._ + val s = streams.value + val args = spaceDelimited().parsed + runLocal(args, s.log) + } + + def runLocalTemplate(arguments: Seq[String], log: Logger): Unit = + TemplateCommandUtil.defaultRunLocalTemplate(arguments.toList, log) } object Classpaths { diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index c64bbbcac..9c8d1855c 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -571,6 +571,8 @@ object Keys { val sbtBinaryVersion = settingKey[String]("Defines the binary compatibility version substring.").withRank(BPlusSetting) val skip = taskKey[Boolean]("For tasks that support it (currently only 'compile', 'update', 'publish' and 'publishLocal'), setting skip to true will force the task to not to do its work. The exact semantics may vary depending on the task.").withRank(BSetting) val templateResolverInfos = settingKey[Seq[TemplateResolverInfo]]("Template resolvers used for 'new'.").withRank(BSetting) + val templateDescriptions = settingKey[Seq[(String, String)]]("List of templates with description used for 'new' / 'init'.") + val templateRunLocal = inputKey[Unit]("Runs a local template.").withRank(DTask) val interactionService = taskKey[InteractionService]("Service used to ask for user input through the current user interface(s).").withRank(CTask) val insideCI = SettingKey[Boolean]("insideCI", "Determines if the sbt is running in a Continuous Integration environment", AMinusSetting) diff --git a/main/src/main/scala/sbt/TemplateCommandUtil.scala b/main/src/main/scala/sbt/TemplateCommandUtil.scala index 5a09fd190..41db2d389 100644 --- a/main/src/main/scala/sbt/TemplateCommandUtil.scala +++ b/main/src/main/scala/sbt/TemplateCommandUtil.scala @@ -41,6 +41,7 @@ private[sbt] object TemplateCommandUtil { val extracted = (Project extract s0) val (s1, ivyConf) = extracted.runTask(Keys.ivyConfiguration, s0) val scalaModuleInfo = extracted.get(Keys.updateSbtClassifiers / Keys.scalaModuleInfo) + val templateDescriptions = extracted.get(Keys.templateDescriptions) val args0 = inputArg.toList ++ (s0.remainingCommands match { case exec :: Nil if exec.commandLine == "shell" => Nil @@ -52,10 +53,10 @@ private[sbt] object TemplateCommandUtil { run(infos, args0, s0.configuration, ivyConf, globalBase, scalaModuleInfo, log) terminate } else { - fortifyArgs() match { + fortifyArgs(templateDescriptions.toList) match { case Nil => terminate case arg :: Nil if arg.endsWith(".local") => - localRun(arg :: Nil, log) + extracted.runInputTask(Keys.templateRunLocal, " " + arg, s0) reload case args => run(infos, args, s0.configuration, ivyConf, globalBase, scalaModuleInfo, log) @@ -166,7 +167,7 @@ private[sbt] object TemplateCommandUtil { private lazy val term: ITerminal = ITerminal.get private lazy val isAnsiSupported = term.isAnsiSupported private lazy val nonMoveLetters = ('a' to 'z').toList diff List('h', 'j', 'k', 'l', 'q') - private lazy val templates = List( + private[sbt] lazy val defaultTemplateDescriptions = List( ScalaToolkitSlug -> "Scala Toolkit (beta) by Scala Center and VirtusLab", TypelevelToolkitSlug -> "Toolkit to start building Typelevel apps", SbtCrossPlatformSlug -> "A cross-JVM/JS/Native project", @@ -179,10 +180,11 @@ private[sbt] object TemplateCommandUtil { "spotify/scio.g8" -> "A Scio project", "disneystreaming/smithy4s.g8" -> "A Smithy4s project", ) - private def fortifyArgs(): List[String] = + private def fortifyArgs(templates: List[(String, String)]): List[String] = if (System.console eq null) Nil else ITerminal.withStreams(true, false) { + assert(templates.size <= 20, "template list cannot have more than 20 items") val mappingList = templates.zipWithIndex.map { case (v, idx) => toLetter(idx) -> v } @@ -261,7 +263,9 @@ private[sbt] object TemplateCommandUtil { else ans0 } - private def localRun( + // This is used by Defaults.runLocalTemplate, which implements + // templateRunLocal input task. + private[sbt] def defaultRunLocalTemplate( arguments: List[String], log: Logger ): Unit = diff --git a/main/src/main/scala/sbt/internal/TaskProgress.scala b/main/src/main/scala/sbt/internal/TaskProgress.scala index 65fea78fc..25effeb81 100644 --- a/main/src/main/scala/sbt/internal/TaskProgress.scala +++ b/main/src/main/scala/sbt/internal/TaskProgress.scala @@ -149,7 +149,9 @@ private[sbt] class TaskProgress( "console", "consoleProject", "consoleQuick", - "state" + "state", + "streams", + "streams-manager", ) private[this] val hiddenTasks = Set( "compileEarly",