diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 3fb29afca..c883c42dd 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -9,6 +9,7 @@ package sbt import java.io.{ File, IOException } import java.net.URI +import java.nio.file.{ FileAlreadyExistsException, Files } import java.util.concurrent.ForkJoinPool import java.util.concurrent.atomic.AtomicBoolean import java.util.{ Locale, Properties } @@ -214,6 +215,7 @@ object BuiltinCommands { plugins, addPluginSbtFile, writeSbtVersion, + skipBanner, notifyUsersAboutShell, shell, startServer, @@ -862,7 +864,8 @@ object BuiltinCommands { def shell: Command = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s0 => import sbt.internal.{ ConsolePromptEvent, ConsoleUnpromptEvent } val exchange = StandardMain.exchange - val s1 = exchange run s0 + val welcomeState = displayWelcomeBanner(s0) + val s1 = exchange run welcomeState exchange publishEventMessage ConsolePromptEvent(s0) val minGCInterval = Project .extract(s1) @@ -943,4 +946,35 @@ object BuiltinCommands { Command.command(NotifyUsersAboutShell) { state => notifyUsersAboutShell(state); state } + + private[this] def skipWelcomeFile(state: State, version: String) = { + val base = BuildPaths.getGlobalBase(state).toPath + base.resolve("preferences").resolve(version).resolve(SkipBannerFileName) + } + private def displayWelcomeBanner(state: State): State = { + if (!state.get(bannerHasBeenShown).getOrElse(false)) { + try { + val version = sbtVersion(state) + val skipFile = skipWelcomeFile(state, version) + Files.createDirectories(skipFile.getParent) + val suppress = !SysProp.banner || Files.exists(skipFile) + if (!suppress) state.log.info(Banner(version)) + } catch { case _: IOException => /* Don't let errors in this command prevent startup */ } + state.put(bannerHasBeenShown, true) + } else state + } + private[this] val bannerHasBeenShown = + AttributeKey[Boolean]("banner-has-been-shown", Int.MaxValue) + private[this] val SkipBannerFileName = "skip-banner" + private[this] val SkipBanner = "skipBanner" + private[this] def skipBanner: Command = Command.command(SkipBanner)(skipBanner) + private def skipBanner(state: State): State = { + val skipFile = skipWelcomeFile(state, sbtVersion(state)) + try Files.createFile(skipFile) + catch { + case _: FileAlreadyExistsException => + case e: IOException => state.log.error(s"Couldn't create file $skipFile: $e") + } + state + } } diff --git a/main/src/main/scala/sbt/internal/Banner.scala b/main/src/main/scala/sbt/internal/Banner.scala new file mode 100644 index 000000000..767903a83 --- /dev/null +++ b/main/src/main/scala/sbt/internal/Banner.scala @@ -0,0 +1,21 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +private[sbt] object Banner { + def apply(version: String): String = + s""" + |Welcome to sbt $version. + |Here are some highlights of this release: + | - Coursier: new default library management using https://get-coursier.io + | - Super shell: displays actively running tasks + | - Turbo mode: makes `test` and `run` faster in interactive sessions. Try it by running `set ThisBuild / turbo := true`. + |See https://www.lightbend.com/blog/sbt-1.3.0-release for full release notes. + |Hide the banner for this release by running `skipBanner`. + |""".stripMargin.linesIterator.filter(_.nonEmpty).mkString("\n") +} diff --git a/main/src/main/scala/sbt/internal/SysProp.scala b/main/src/main/scala/sbt/internal/SysProp.scala index c5dd716f0..138ec14c6 100644 --- a/main/src/main/scala/sbt/internal/SysProp.scala +++ b/main/src/main/scala/sbt/internal/SysProp.scala @@ -93,6 +93,8 @@ object SysProp { coursierOpt.orElse(notIvyOpt).getOrElse(true) } + def banner: Boolean = getOrTrue("sbt.banner") + def turbo: Boolean = getOrFalse("sbt.turbo") def taskTimings: Boolean = getOrFalse("sbt.task.timings")