diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/VersionOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/VersionOptions.scala index 4fe66dc2f3..1963e0ce36 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/VersionOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/VersionOptions.scala @@ -6,7 +6,13 @@ import caseapp._ @HelpMessage("Print `scala-cli` version") final case class VersionOptions( @Recurse - verbosity: VerbosityOptions = VerbosityOptions() + verbosity: VerbosityOptions = VerbosityOptions(), + @HelpMessage("Show only plain scala-cli version") + @Name("cli") + cliVersion: Boolean = false, + @HelpMessage("Show only plain scala version") + @Name("scala") + scalaVersion: Boolean = false ) // format: on diff --git a/modules/cli/src/main/java/scala/cli/internal/Argv0SubstWindows.java b/modules/cli/src/main/java/scala/cli/internal/Argv0SubstWindows.java new file mode 100644 index 0000000000..1d95a15e4b --- /dev/null +++ b/modules/cli/src/main/java/scala/cli/internal/Argv0SubstWindows.java @@ -0,0 +1,19 @@ +package scala.cli.internal; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import java.nio.file.Path; + +@TargetClass(className = "scala.cli.internal.Argv0") +@Platforms({Platform.WINDOWS.class}) +final class Argv0SubstWindows { + + @Substitute + String get(String defaultValue) { + return coursier.jniutils.ModuleFileName.get(); + } + +} diff --git a/modules/cli/src/main/scala/scala/cli/ScalaCli.scala b/modules/cli/src/main/scala/scala/cli/ScalaCli.scala index beaa615b1b..fc9f05a5ee 100644 --- a/modules/cli/src/main/scala/scala/cli/ScalaCli.scala +++ b/modules/cli/src/main/scala/scala/cli/ScalaCli.scala @@ -16,12 +16,20 @@ import scala.util.Properties object ScalaCli { + if (Properties.isWin && isGraalvmNativeImage) + // have to be initialized before running (new Argv0).get because Argv0SubstWindows uses csjniutils library + // The DLL loaded by LoadWindowsLibrary is statically linke/d in + // the Scala CLI native image, no need to manually load it. + coursier.jniutils.LoadWindowsLibrary.assumeInitialized() + val progName = (new Argv0).get("scala-cli") - private def checkName(name: String) = - progName == name || - progName.endsWith(s"/$name") || - progName.endsWith(File.separator + name) + private def checkName(name: String) = { + val baseProgName = if (Properties.isWin) progName.stripSuffix(".exe") else progName + baseProgName == name || + baseProgName.endsWith(s"/$name") || + baseProgName.endsWith(File.separator + name) + } private var isSipScala = checkName("scala") || checkName("scala-cli-sip") @@ -171,11 +179,6 @@ object ScalaCli { if (!Properties.isWin && isGraalvmNativeImage) ignoreSigpipe() - if (Properties.isWin && isGraalvmNativeImage) - // The DLL loaded by LoadWindowsLibrary is statically linked in - // the Scala CLI native image, no need to manually load it. - coursier.jniutils.LoadWindowsLibrary.assumeInitialized() - if (Properties.isWin && System.console() != null && coursier.paths.Util.useJni()) // Enable ANSI output in Windows terminal coursier.jniutils.WindowsAnsiTerminal.enableAnsiOutput() diff --git a/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala b/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala index 3bf5b3b615..099d9b5cde 100644 --- a/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala +++ b/modules/cli/src/main/scala/scala/cli/ScalaCliCommands.scala @@ -18,7 +18,7 @@ class ScalaCliCommands( isSipScala: Boolean ) extends CommandsEntryPoint { - lazy val actualDefaultCommand = new Default(help) + lazy val actualDefaultCommand = new Default(help, isSipScala) // for debugging purposes - allows to run the scala-cli-signing binary from the Scala CLI JVM launcher private lazy val pgpUseBinaryCommands = @@ -64,7 +64,7 @@ class ScalaCliCommands( Uninstall, UninstallCompletions, Update, - Version + new Version(isSipScala = isSipScala) ) ++ (if (pgpUseBinaryCommands) Nil else pgpCommands.allScalaCommands.toSeq) ++ (if (pgpUseBinaryCommands) pgpBinaryCommands.allScalaCommands.toSeq else Nil) diff --git a/modules/cli/src/main/scala/scala/cli/commands/About.scala b/modules/cli/src/main/scala/scala/cli/commands/About.scala index 60713824d4..567a6e06e7 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/About.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/About.scala @@ -11,12 +11,7 @@ class About(isSipScala: Boolean) extends ScalaCommand[AboutOptions] { def run(options: AboutOptions, args: RemainingArgs): Unit = { CurrentParams.verbosity = options.verbosity.verbosity - val version = Constants.version - val detailedVersionOpt = Constants.detailedVersion.filter(_ != version) - val appName = - if (isSipScala) "Scala command" - else "Scala CLI" - println(s"$appName version $version" + detailedVersionOpt.fold("")(" (" + _ + ")")) + println(Version.versionInfo(isSipScala)) val newestScalaCliVersion = Update.newestScalaCliVersion(options.ghToken.map(_.get())) val isOutdated = CommandUtils.isOutOfDateVersion( newestScalaCliVersion, diff --git a/modules/cli/src/main/scala/scala/cli/commands/Default.scala b/modules/cli/src/main/scala/scala/cli/commands/Default.scala index 0874dae9f2..8516d5c255 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Default.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Default.scala @@ -7,7 +7,8 @@ import scala.build.internal.Constants import scala.cli.{CurrentParams, ScalaCliHelp} class Default( - actualHelp: => RuntimeCommandsHelp + actualHelp: => RuntimeCommandsHelp, + isSipScala: Boolean ) extends ScalaCommand[DefaultOptions] { private def defaultHelp: String = actualHelp.help(ScalaCliHelp.helpFormat) @@ -30,7 +31,7 @@ class Default( def run(options: DefaultOptions, args: RemainingArgs): Unit = { CurrentParams.verbosity = options.runOptions.shared.logging.verbosity if (options.version) - println(Constants.version) + println(Version.versionInfo(isSipScala)) else if (anyArgs) Run.run( options.runOptions, diff --git a/modules/cli/src/main/scala/scala/cli/commands/InstallHome.scala b/modules/cli/src/main/scala/scala/cli/commands/InstallHome.scala index 849245c164..330aca77b7 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/InstallHome.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/InstallHome.scala @@ -51,12 +51,12 @@ object InstallHome extends ScalaCommand[InstallHomeOptions] { val newScalaCliBinPath = os.Path(options.scalaCliBinaryPath, os.pwd) val newVersion: String = - os.proc(newScalaCliBinPath, "version").call(cwd = os.pwd).out.text().trim + os.proc(newScalaCliBinPath, "version", "--cli-version").call(cwd = os.pwd).out.text().trim // Backward compatibility - previous versions not have the `--version` parameter val oldVersion: String = if (os.isFile(destBinPath)) { - val res = os.proc(destBinPath, "version").call(cwd = os.pwd, check = false) + val res = os.proc(destBinPath, "version", "--cli-version").call(cwd = os.pwd, check = false) if (res.exitCode == 0) res.out.text().trim else diff --git a/modules/cli/src/main/scala/scala/cli/commands/Update.scala b/modules/cli/src/main/scala/scala/cli/commands/Update.scala index 8c55489181..d742d1974d 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Update.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Update.scala @@ -95,7 +95,7 @@ object Update extends ScalaCommand[UpdateOptions] { } private def getCurrentVersion(scalaCliBinPath: os.Path): String = { - val res = os.proc(scalaCliBinPath, "version").call(cwd = os.pwd, check = false) + val res = os.proc(scalaCliBinPath, "version", "--cli-version").call(cwd = os.pwd, check = false) if (res.exitCode == 0) res.out.text().trim else diff --git a/modules/cli/src/main/scala/scala/cli/commands/Version.scala b/modules/cli/src/main/scala/scala/cli/commands/Version.scala index 0adf8ce974..36db312041 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Version.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Version.scala @@ -5,10 +5,26 @@ import caseapp._ import scala.build.internal.Constants import scala.cli.CurrentParams -object Version extends ScalaCommand[VersionOptions] { +class Version(isSipScala: Boolean) extends ScalaCommand[VersionOptions] { override def group = "Miscellaneous" def run(options: VersionOptions, args: RemainingArgs): Unit = { CurrentParams.verbosity = options.verbosity.verbosity - println(Constants.version) + if (options.cliVersion) + println(Constants.version) + else if (options.scalaVersion) + println(Constants.defaultScalaVersion) + else + println(Version.versionInfo(isSipScala)) } } + +object Version { + def versionInfo(isSipScala: Boolean) = + val version = Constants.version + val detailedVersionOpt = Constants.detailedVersion.filter(_ != version).fold("")(" (" + _ + ")") + val appName = + if (isSipScala) "Scala code runner" + else "Scala CLI" + s"""$appName version: $version$detailedVersionOpt + |Scala version (default): ${Constants.defaultScalaVersion}""".stripMargin +} diff --git a/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala b/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala index f041bd34d6..4a8640d181 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala @@ -39,4 +39,35 @@ class SipScalaTests extends ScalaCliSuite { noDirectoriesCommandTest("scala-cli-sip") } } + + def runVersionCommand(binaryName: String) = + TestInputs.empty.fromRoot { root => + val cliPath = os.Path(TestUtil.cliPath, os.pwd) + val ext = if (Properties.isWin) ".exe" else "" + val newCliPath = root / s"$binaryName$ext" + os.copy(cliPath, newCliPath) + + for { versionOption <- Seq("version", "-version", "--version") } { + val version = os.proc(newCliPath, versionOption).call(check = false) + assert( + version.exitCode == 0, + clues(version, version.out.text(), version.err.text(), version.exitCode) + ) + val expectedLauncherVersion = + if (binaryName == "scala") "Scala code runner version:" + else "Scala CLI version:" + expect(version.out.text().contains(expectedLauncherVersion)) + expect(version.out.text().contains(s"Scala version (default): ${Constants.defaultScala}")) + } + } + + if (TestUtil.isNativeCli) { + test("version command print detailed info run as scala") { + runVersionCommand("scala") + } + + test("version command print detailed info run as scala-cli") { + runVersionCommand("scala-cli") + } + } } diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 35e0a10b7d..c637a8fb83 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -1976,6 +1976,26 @@ Interactive mode Enable actionable diagnostics +## Version options + +Available in commands: +- [`version`](./commands.md#version) + + + + +#### `--cli-version` + +Aliases: `--cli` + +Show only plain scala-cli version + +#### `--scala-version` + +Aliases: `--scala` + +Show only plain scala version + ## Watch options Available in commands: diff --git a/website/docs/reference/commands.md b/website/docs/reference/commands.md index 26e52ef2db..6fb147849a 100644 --- a/website/docs/reference/commands.md +++ b/website/docs/reference/commands.md @@ -476,6 +476,7 @@ Print `scala-cli` version Accepts options: - [verbosity](./cli-options.md#verbosity-options) +- [version](./cli-options.md#version-options) ## Hidden commands