Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more interactive install-home command #186

Merged
merged 7 commits into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion modules/cli/src/main/scala/scala/cli/ScalaCli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ object ScalaCli extends CommandsEntryPoint {
Package,
Run,
SetupIde,
Test
Test,
Version
)

lazy val progName = (new Argv0).get("scala-cli")
Expand Down
118 changes: 78 additions & 40 deletions modules/cli/src/main/scala/scala/cli/commands/InstallHome.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,98 @@ import caseapp._
import coursier.env.{EnvironmentUpdate, ProfileUpdater}

import scala.io.StdIn.readLine
import scala.util.Properties
import scala.util.{Properties, Try}

object InstallHome extends ScalaCommand[InstallHomeOptions] {
override def hidden: Boolean = true
def run(options: InstallHomeOptions, args: RemainingArgs): Unit = {

val binDirPath = scala.build.Directories.default().binRepoDir
val scalaCliBinPath = binDirPath / "scala-cli"

if (os.exists(scalaCliBinPath))
if (options.force) os.remove.all(scalaCliBinPath)
else if (coursier.paths.Util.useAnsiOutput()) {
println(
"scala-cli already exists. Do you want to override it [Y/n]"
)
val replace = readLine()
if (replace == "Y")
os.remove.all(scalaCliBinPath)
else {
System.err.println("Abort")
sys.exit(1)
}
}
else {
System.err.println(
s"Error: scala-cli already exists. Pass -f or --force to force erasing it."
)
private def isOutOfDate(newVersion: String, oldVersion: String): Boolean = {
import coursier.core.Version

Version(newVersion) > Version(oldVersion)
}

private def logEqual(version: String) = {
System.err.println(
s"Scala-cli $version is already installed and up-to-date."
)
sys.exit(0)
}

private def logUpdate(env: Boolean, newVersion: String, oldVersion: String) =
if (!env) println(
s"""scala-cli $oldVersion is already installed and out-of-date.
|scala-cli will be updated to version $newVersion
|""".stripMargin
)

private def logDowngrade(env: Boolean, newVersion: String, oldVersion: String) =
if (!env && coursier.paths.Util.useAnsiOutput()) {
println(s"scala-cli $oldVersion is already installed and up-to-date.")
println(s"Do you want to downgrade scala-cli to version $newVersion [Y/n]")
val response = readLine()
if (response != "Y") {
System.err.println("Abort")
sys.exit(1)
}
}
else {
System.err.println(
s"Error: scala-cli is already installed $oldVersion and up-to-date. Downgrade to $newVersion pass -f or --force."
)
sys.exit(1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to put exit in a function named logDowngrade. Minor issue, can be done in a follow-up PR

}

def run(options: InstallHomeOptions, args: RemainingArgs): Unit = {

val binDirPath =
options.binDirPath.getOrElse(scala.build.Directories.default().binRepoDir / "scala-cli")
val newScalaCliBinPath = os.Path(options.scalaCliBinaryPath, os.pwd)

val newVersion: String =
os.proc(newScalaCliBinPath, "version").call(cwd = os.pwd).out.text.trim

// Backward compatibility - previous versions not have the `--version` parameter
val oldVersion: String = Try {
os.proc(binDirPath / options.binaryName, "version").call(cwd = os.pwd).out.text.trim
}.toOption.getOrElse("0.0.0")

if (os.exists(binDirPath))
if (options.force) () // skip logging
else if (newVersion == oldVersion) logEqual(newVersion)
else if (isOutOfDate(newVersion, oldVersion))
logUpdate(options.env, newVersion, oldVersion)
else logDowngrade(options.env, newVersion, oldVersion)

os.remove.all(binDirPath)

os.copy(
from = os.Path(options.scalaCliBinaryPath, os.pwd),
to = scalaCliBinPath / "scala-cli",
from = newScalaCliBinPath,
to = binDirPath / options.binaryName,
createFolders = true
)
if (!Properties.isWin)
os.perms.set(scalaCliBinPath / "scala-cli", os.PermSet.fromString("rwxrwxr-x"))
os.perms.set(binDirPath / options.binaryName, os.PermSet.fromString("rwxr-xr-x"))
lwronski marked this conversation as resolved.
Show resolved Hide resolved

val update = EnvironmentUpdate(Nil, Seq("PATH" -> scalaCliBinPath.toString()))
if (options.env)
println(s"""export PATH="$binDirPath:$$PATH"""")
else {

val didUpdate =
if (Properties.isWin) {
val updater = CustomWindowsEnvVarUpdater().withUseJni(Some(coursier.paths.Util.useJni()))
updater.applyUpdate(update)
}
else {
val updater = ProfileUpdater()
updater.applyUpdate(update)
}
val update = EnvironmentUpdate(Nil, Seq("PATH" -> binDirPath.toString()))

val didUpdate =
if (Properties.isWin) {
val updater = CustomWindowsEnvVarUpdater().withUseJni(Some(coursier.paths.Util.useJni()))
updater.applyUpdate(update)
}
else {
val updater = ProfileUpdater()
updater.applyUpdate(update)
}

if (didUpdate)
println("Successfully installed scala-cli")
else
System.err.println(s"scala-cli is already installed")
if (didUpdate) "Profile was updated"

println(s"Successfully installed scala-cli $newVersion")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ final case class InstallHomeOptions(
@Name("f")
@HelpMessage("Overwrite scala-cli if exists")
force: Boolean = false,
)
// format: on
@HelpMessage("Binary name")
binaryName: String = "scala-cli",
@HelpMessage("Print the env update")
env: Boolean = false,
@HelpMessage("Binary directory")
binDir: Option[String] = None
) {
// format: on
lazy val binDirPath = binDir.map(os.Path(_, os.pwd))
}

object InstallHomeOptions {
implicit lazy val parser: Parser[InstallHomeOptions] = Parser.derive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package scala.cli.commands

import caseapp._
import com.google.gson.Gson
import coursier.core.Version

import java.io.{BufferedReader, File, FileReader}
import java.nio.file.{AtomicMoveNotSupportedException, FileAlreadyExistsException, Files}
Expand Down Expand Up @@ -168,7 +167,9 @@ final case class SharedCompilationServerOptions(
parseDuration("connection server startup timeout", bloopStartupTimeout)

def minimumBloopVersion = Constants.bloopVersion
def acceptBloopVersion = Some((v: String) => Version(v) < Version(minimumBloopVersion))

import coursier.core.Version
def acceptBloopVersion = Some((v: String) => Version(v) < Version(minimumBloopVersion))

def bloopDefaultJvmOptions(logger: Logger): List[String] = {
val file = new File(bloopGlobalOptionsFile)
Expand Down
12 changes: 12 additions & 0 deletions modules/cli/src/main/scala/scala/cli/commands/Version.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package scala.cli.commands

import caseapp._

import scala.build.internal.Constants

object Version extends ScalaCommand[VersionOptions] {
override def group = "Miscellaneous"
def run(options: VersionOptions, args: RemainingArgs): Unit = {
println(Constants.version)
}
}
11 changes: 11 additions & 0 deletions modules/cli/src/main/scala/scala/cli/commands/VersionOptions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package scala.cli.commands

import caseapp._

@HelpMessage("Print scala-cli version")
final case class VersionOptions()

object VersionOptions {
implicit lazy val parser: Parser[VersionOptions] = Parser.derive
implicit lazy val help: Help[RunOptions] = Help.derive
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package scala.cli.integration

import com.eed3si9n.expecty.Expecty.expect

import scala.util.Properties

class InstallHomeTests extends munit.FunSuite {

val firstVersion = "0.0.1"
val secondVersion = "0.0.2"
val dummyScalaCliFirstName = "DummyScalaCli-1.scala"
val dummyScalaCliSecondName = "DummyScalaCli-2.scala"
val dummyScalaCliBinName = "scala-cli-dummy-test"
val testInputs = TestInputs(
Seq(
os.rel / dummyScalaCliFirstName ->
s"""
|object DummyScalaCli extends App {
| println(\"$firstVersion\")
|}""".stripMargin,
os.rel / dummyScalaCliSecondName ->
s"""
|object DummyScalaCli extends App {
| println(\"$secondVersion\")
|}""".stripMargin
)
)

private def packageDummyScalaCli(root: os.Path, dummyScalaCliFileName: String, output: String) = {
// format: off
val cmd = Seq[os.Shellable](
TestUtil.cli, "package", dummyScalaCliFileName, "-o", output
)
// format: on
os.proc(cmd).call(
cwd = root,
stdin = os.Inherit,
stdout = os.Inherit
)
}

private def installScalaCli(
root: os.Path,
binVersion: String,
binDirPath: os.Path,
force: Boolean
) = {
// format: off
val cmdInstallVersion = Seq[os.Shellable](
TestUtil.cli, "install-home",
"--env",
"--scala-cli-binary-path", binVersion ,
"--binary-name", dummyScalaCliBinName,
"--bin-dir", binDirPath
) ++ (if(force) Seq[os.Shellable]("--force") else Seq.empty)
// format: on
os.proc(cmdInstallVersion).call(
cwd = root,
stdin = os.Inherit,
stdout = os.Inherit
)
}

def runInstallHome(): Unit = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very sophisticated test, took me some time that we build fake scala-cli executables that can print only version and juggle them. Maybe we could comment it (in at all)


testInputs.fromRoot { root =>
val binDirPath = root / ".scala" / "scala-cli"

val binDummyScalaCliFirst = dummyScalaCliFirstName.stripSuffix(".scala").toLowerCase
val binDummyScalaCliSecond = dummyScalaCliSecondName.stripSuffix(".scala").toLowerCase

packageDummyScalaCli(root, dummyScalaCliFirstName, binDummyScalaCliFirst)
packageDummyScalaCli(root, dummyScalaCliSecondName, binDummyScalaCliSecond)

// install 1 version
installScalaCli(root, binDummyScalaCliFirst, binDirPath, force = true)

println(binDirPath / dummyScalaCliBinName)

val v1Install = os.proc(binDirPath / dummyScalaCliBinName).call(
cwd = root,
stdin = os.Inherit
).out.text.trim
expect(v1Install == firstVersion)

// update to 2 version
installScalaCli(root, binDummyScalaCliSecond, binDirPath, force = false)

val v2Update = os.proc(binDirPath / dummyScalaCliBinName).call(
cwd = root,
stdin = os.Inherit
).out.text.trim
expect(v2Update == secondVersion)

// downgrade to 1 version with force
installScalaCli(root, binDummyScalaCliFirst, binDirPath, force = true)

val v1Downgrade = os.proc(binDirPath / dummyScalaCliBinName).call(
cwd = root,
stdin = os.Inherit
).out.text.trim
expect(v1Downgrade == firstVersion)
}
}

if (!Properties.isWin && TestUtil.isCI)
test("updating and downgrading dummy scala-cli using install-home command") {
runInstallHome()
}

}
13 changes: 13 additions & 0 deletions website/docs/reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ Available in commands:
- [`run`](./commands#run)
- [`setup-ide`](./commands#setup-ide)
- [`test`](./commands#test)
- [`version`](./commands#version)


<!-- Automatically generated, DO NOT EDIT MANUALLY -->
Expand Down Expand Up @@ -343,6 +344,18 @@ Aliases: `-f`

Overwrite scala-cli if exists

#### `--binary-name`

Binary name

#### `--env`

Print the env update

#### `--bin-dir`

Binary directory

## Java options

Available in commands:
Expand Down
4 changes: 4 additions & 0 deletions website/docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ Accepts options:
- [test](./cli-options.md#test-options)
- [watch](./cli-options.md#watch-options)

## `version`

Print scala-cli version

## Hidden commands

### `add-path`
Expand Down