Skip to content

Commit

Permalink
Merge pull request #2931 from Gedochao/maintenance/make-bsp-respect-l…
Browse files Browse the repository at this point in the history
…auncher-args

Improve launcher options handling
  • Loading branch information
Gedochao authored May 24, 2024
2 parents 06ddc00 + 6fefa30 commit c04ef18
Show file tree
Hide file tree
Showing 17 changed files with 324 additions and 96 deletions.
24 changes: 10 additions & 14 deletions modules/cli/src/main/scala/scala/cli/ScalaCli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ object ScalaCli {
private def isGraalvmNativeImage: Boolean =
sys.props.contains("org.graalvm.nativeimage.imagecode")

private var defaultScalaVersion: Option[String] = None
val launcherPredefinedRepositories: ListBuffer[String] = ListBuffer.empty
private var maybeLauncherOptions: Option[LauncherOptions] = None

def getDefaultScalaVersion: String = defaultScalaVersion.getOrElse(Constants.defaultScalaVersion)
def launcherOptions: LauncherOptions = maybeLauncherOptions.getOrElse(LauncherOptions())
def getDefaultScalaVersion: String =
launcherOptions.scalaRunner.cliUserScalaVersion.getOrElse(Constants.defaultScalaVersion)

private def partitionArgs(args: Array[String]): (Array[String], Array[String]) = {
val systemProps = args.takeWhile(_.startsWith("-D"))
Expand Down Expand Up @@ -232,25 +233,20 @@ object ScalaCli {
System.err.println(e.message)
sys.exit(1)
case Right((launcherOpts, args0)) =>
maybeLauncherOptions = Some(launcherOpts)
launcherOpts.cliVersion.map(_.trim).filter(_.nonEmpty) match {
case Some(ver) =>
val powerArgs =
if (launcherOpts.powerOptions.power) Seq("--power")
else Nil
val newArgs = powerArgs ++ args0
val powerArgs = launcherOpts.powerOptions.toCliArgs
val scalaRunnerArgs = launcherOpts.scalaRunner.toCliArgs
val newArgs = powerArgs ++ scalaRunnerArgs ++ args0
LauncherCli.runAndExit(ver, launcherOpts, newArgs)
case _ if
javaMajorVersion < 17
&& sys.props.get("scala-cli.kind").exists(_.startsWith("jvm")) =>
JavaLauncherCli.runAndExit(args)
case None =>
launcherOpts.progName.foreach { pn =>
progName = pn
}
if launcherOpts.cliUserScalaVersion.nonEmpty then
defaultScalaVersion = launcherOpts.cliUserScalaVersion
if launcherOpts.cliPredefinedRepository.nonEmpty then
launcherPredefinedRepositories.addAll(launcherOpts.cliPredefinedRepository)
launcherOpts.scalaRunner.progName
.foreach(pn => progName = pn)
if launcherOpts.powerOptions.power then
isSipScala = false
args0.toArray
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import caseapp.core.{Arg, Error, RemainingArgs}
import caseapp.{HelpMessage, Name}
import coursier.core.{Repository, Version}
import dependency.*
import org.codehaus.plexus.classworlds.launcher.Launcher

import java.util.concurrent.atomic.{AtomicBoolean, AtomicReference}

Expand All @@ -28,6 +29,7 @@ import scala.cli.commands.util.CommandHelpers
import scala.cli.commands.util.ScalacOptionsUtil.*
import scala.cli.config.{ConfigDb, Keys}
import scala.cli.internal.ProcUtil
import scala.cli.launcher.LauncherOptions
import scala.cli.util.ConfigDbUtils.*
import scala.cli.{CurrentParams, ScalaCli}
import scala.util.{Properties, Try}
Expand All @@ -38,8 +40,9 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T],
private val globalOptionsAtomic: AtomicReference[GlobalOptions] =
new AtomicReference(GlobalOptions.default)

private def globalOptions: GlobalOptions = globalOptionsAtomic.get()
protected def defaultScalaVersion: String = ScalaCli.getDefaultScalaVersion
private def globalOptions: GlobalOptions = globalOptionsAtomic.get()
protected def launcherOptions: LauncherOptions = ScalaCli.launcherOptions
protected def defaultScalaVersion: String = ScalaCli.getDefaultScalaVersion

def sharedOptions(t: T): Option[SharedOptions] = // hello borked unused warning
None
Expand Down
53 changes: 39 additions & 14 deletions modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import scala.cli.commands.ScalaCommand
import scala.cli.commands.publish.ConfigUtil.*
import scala.cli.commands.shared.SharedOptions
import scala.cli.config.{ConfigDb, Keys}
import scala.cli.launcher.LauncherOptions
import scala.concurrent.Await
import scala.concurrent.duration.Duration

Expand All @@ -29,6 +30,14 @@ object Bsp extends ScalaCommand[BspOptions] {
val content = os.read.bytes(os.Path(optionsPath, os.pwd))
readFromArray(content)(SharedOptions.jsonCodec)
}.getOrElse(options.shared)
private def latestLauncherOptions(options: BspOptions): LauncherOptions =
options.jsonLauncherOptions
.map(path => os.Path(path, os.pwd))
.filter(path => os.exists(path) && os.isFile(path))
.map { optionsPath =>
val content = os.read.bytes(os.Path(optionsPath, os.pwd))
readFromArray(content)(LauncherOptions.jsonCodec)
}.getOrElse(launcherOptions)
private def latestEnvsFromFile(options: BspOptions): Map[String, String] =
options.envs
.map(path => os.Path(path, os.pwd))
Expand All @@ -48,19 +57,21 @@ object Bsp extends ScalaCommand[BspOptions] {
pprint.err.log(args)

val getSharedOptions: () => SharedOptions = () => latestSharedOptions(options)
val getLauncherOptions: () => LauncherOptions = () => latestLauncherOptions(options)
val getEnvsFromFile: () => Map[String, String] = () => latestEnvsFromFile(options)

val preprocessInputs: Seq[String] => Either[BuildException, (Inputs, BuildOptions)] =
argsSeq =>
either {
val sharedOptions = getSharedOptions()
val envs = getEnvsFromFile()
val initialInputs = value(sharedOptions.inputs(argsSeq, () => Inputs.default()))
val sharedOptions = getSharedOptions()
val launcherOptions = getLauncherOptions()
val envs = getEnvsFromFile()
val initialInputs = value(sharedOptions.inputs(argsSeq, () => Inputs.default()))

if (sharedOptions.logging.verbosity >= 3)
pprint.err.log(initialInputs)

val baseOptions = buildOptions(sharedOptions, envs)
val baseOptions = buildOptions(sharedOptions, launcherOptions, envs)
val latestLogger = sharedOptions.logging.logger
val persistentLogger = new PersistentDiagnosticLogger(latestLogger)

Expand Down Expand Up @@ -99,8 +110,10 @@ object Bsp extends ScalaCommand[BspOptions] {
*/
val initialBspOptions = {
val sharedOptions = getSharedOptions()
val launcherOptions = getLauncherOptions()
val envs = getEnvsFromFile()
val bspBuildOptions = buildOptions(sharedOptions, envs).orElse(finalBuildOptions)
val bspBuildOptions = buildOptions(sharedOptions, launcherOptions, envs)
.orElse(finalBuildOptions)
BspReloadableOptions(
buildOptions = bspBuildOptions,
bloopRifleConfig = sharedOptions.bloopRifleConfig(Some(bspBuildOptions))
Expand All @@ -111,13 +124,14 @@ object Bsp extends ScalaCommand[BspOptions] {
}

val bspReloadableOptionsReference = BspReloadableOptions.Reference { () =>
val sharedOptions = getSharedOptions()
val envs = getEnvsFromFile()
val sharedOptions = getSharedOptions()
val launcherOptions = getLauncherOptions()
val envs = getEnvsFromFile()
val bloopRifleConfig = sharedOptions.bloopRifleConfig(Some(finalBuildOptions))
.orExit(sharedOptions.logger)

BspReloadableOptions(
buildOptions = buildOptions(sharedOptions, envs),
buildOptions = buildOptions(sharedOptions, launcherOptions, envs),
bloopRifleConfig = sharedOptions.bloopRifleConfig().orExit(sharedOptions.logger),
logger = sharedOptions.logging.logger,
verbosity = sharedOptions.logging.verbosity
Expand Down Expand Up @@ -148,11 +162,12 @@ object Bsp extends ScalaCommand[BspOptions] {

private def buildOptions(
sharedOptions: SharedOptions,
launcherOptions: LauncherOptions,
envs: Map[String, String]
): BuildOptions = {
val logger = sharedOptions.logger
val baseOptions = sharedOptions.buildOptions().orExit(logger)
val adjustedOptions = baseOptions.copy(
val withDefaults = baseOptions.copy(
classPathOptions = baseOptions.classPathOptions.copy(
fetchSources = baseOptions.classPathOptions.fetchSources.orElse(Some(true))
),
Expand All @@ -167,18 +182,28 @@ object Bsp extends ScalaCommand[BspOptions] {
baseOptions.notForBloopOptions.addRunnerDependencyOpt.orElse(Some(false))
)
)
envs.get("JAVA_HOME")
.filter(_ => adjustedOptions.javaOptions.javaHomeOpt.isEmpty)
val withEnvs = envs.get("JAVA_HOME")
.filter(_ => withDefaults.javaOptions.javaHomeOpt.isEmpty)
.map(javaHome =>
adjustedOptions.copy(javaOptions =
adjustedOptions.javaOptions.copy(javaHomeOpt =
withDefaults.copy(javaOptions =
withDefaults.javaOptions.copy(javaHomeOpt =
Some(Positioned(
Seq(Position.Custom("ide.env.JAVA_HOME")),
os.Path(javaHome, Os.pwd)
))
)
)
)
.getOrElse(adjustedOptions)
.getOrElse(withDefaults)
val withLauncherOptions = withEnvs.copy(
classPathOptions = withEnvs.classPathOptions.copy(
extraRepositories =
(withEnvs.classPathOptions.extraRepositories ++ launcherOptions.scalaRunner.cliPredefinedRepository).distinct
),
scalaOptions = withEnvs.scalaOptions.copy(
defaultScalaVersion = launcherOptions.scalaRunner.cliUserScalaVersion
)
)
withLauncherOptions
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ final case class BspOptions(
@Tag(tags.implementation)
jsonOptions: Option[String] = None,

@HelpMessage("Command-line launcher options JSON file")
@ValueDescription("path")
@Hidden
@Tag(tags.implementation)
jsonLauncherOptions: Option[String] = None,

@HelpMessage("Command-line options environment variables file")
@ValueDescription("path")
@Hidden
Expand Down
5 changes: 2 additions & 3 deletions modules/cli/src/main/scala/scala/cli/commands/repl/Repl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,18 @@ object Repl extends ScalaCommand[ReplOptions] {
scalaOptions = baseOptions.scalaOptions.copy(
scalaVersion = baseOptions.scalaOptions.scalaVersion
.orElse {
val defaultScalaVer = ScalaCli.getDefaultScalaVersion
val shouldDowngrade = {
def needsDowngradeForAmmonite = {
import coursier.core.Version
Version(maxAmmoniteScalaVer) < Version(defaultScalaVer)
Version(maxAmmoniteScalaVer) < Version(defaultScalaVersion)
}
ammonite.contains(true) &&
ammoniteVersionOpt.isEmpty &&
needsDowngradeForAmmonite
}
if (shouldDowngrade) {
logger.message(
s"Scala $defaultScalaVer is not yet supported with this version of Ammonite"
s"Scala $defaultScalaVersion is not yet supported with this version of Ammonite"
)
logger.message(s"Defaulting to Scala $maxAmmoniteScalaVer")
Some(MaybeScalaVersion(maxAmmoniteScalaVer))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import scala.cli.CurrentParams
import scala.cli.commands.shared.{SharedBspFileOptions, SharedOptions}
import scala.cli.commands.{CommandUtils, ScalaCommand}
import scala.cli.errors.FoundVirtualInputsError
import scala.cli.launcher.LauncherOptions
import scala.jdk.CollectionConverters.*

object SetupIde extends ScalaCommand[SetupIdeOptions] {
Expand Down Expand Up @@ -132,6 +133,8 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
val (bspName, bspJsonDestination) = bspDetails(inputs.workspace, options.bspFile)
val scalaCliBspJsonDestination =
inputs.workspace / Constants.workspaceDirName / "ide-options-v2.json"
val scalaCliBspLauncherOptsJsonDestination =
inputs.workspace / Constants.workspaceDirName / "ide-launcher-options.json"
val scalaCliBspInputsJsonDestination =
inputs.workspace / Constants.workspaceDirName / "ide-inputs.json"
val scalaCliBspEnvsJsonDestination =
Expand All @@ -151,9 +154,12 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
)

val bspArgs =
List(CommandUtils.getAbsolutePathToScalaCli(progName), "bsp") ++
List(CommandUtils.getAbsolutePathToScalaCli(progName)) ++
launcherOptions.toCliArgs ++
List("bsp") ++
debugOpt ++
List("--json-options", scalaCliBspJsonDestination.toString) ++
List("--json-launcher-options", scalaCliBspLauncherOptsJsonDestination.toString) ++
List("--envs-file", scalaCliBspEnvsJsonDestination.toString) ++
inputArgs
val details = new BspConnectionDetails(
Expand All @@ -175,10 +181,11 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {

implicit val mapCodec: JsonValueCodec[Map[String, String]] = JsonCodecMaker.make

val json = gson.toJson(details)
val scalaCliOptionsForBspJson = writeToArray(options.shared)(SharedOptions.jsonCodec)
val scalaCliBspInputsJson = writeToArray(ideInputs)
val scalaCliBspEnvsJson = writeToArray(sys.env)
val json = gson.toJson(details)
val scalaCliOptionsForBspJson = writeToArray(options.shared)(SharedOptions.jsonCodec)
val scalaCliLaunchOptsForBspJson = writeToArray(launcherOptions)(LauncherOptions.jsonCodec)
val scalaCliBspInputsJson = writeToArray(ideInputs)
val scalaCliBspEnvsJson = writeToArray(sys.env)

if (inputs.workspaceOrigin.contains(WorkspaceOrigin.HomeDir))
value(Left(new WorkspaceError(
Expand All @@ -194,6 +201,11 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
scalaCliOptionsForBspJson,
createFolders = true
)
os.write.over(
scalaCliBspLauncherOptsJsonDestination,
scalaCliLaunchOptsForBspJson,
createFolders = true
)
os.write.over(
scalaCliBspInputsJsonDestination,
scalaCliBspInputsJson,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,9 @@ final case class SharedOptions(
extraCompileOnlyJars = extraCompileOnlyClassPath,
extraSourceJars = extraSourceJars.extractedClassPath ++ assumedSourceJars,
extraRepositories =
(dependencies.repository ++ ScalaCli.launcherPredefinedRepositories).map(_.trim).filter(
_.nonEmpty
),
(dependencies.repository ++ ScalaCli.launcherOptions.scalaRunner.cliPredefinedRepository)
.map(_.trim)
.filter(_.nonEmpty),
extraDependencies = ShadowingSeq.from(
SharedOptions.parseDependencies(
dependencies.dependency.map(Positioned.none),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ object Version extends ScalaCommand[VersionOptions] {
else None
}
if options.cliVersion then println(Constants.version)
else if options.scalaVersion then println(ScalaCli.getDefaultScalaVersion)
else if options.scalaVersion then println(defaultScalaVersion)
else {
println(versionInfo)
if !options.offline then
Expand All @@ -51,5 +51,5 @@ object Version extends ScalaCommand[VersionOptions] {
val version = Constants.version
val detailedVersionOpt = Constants.detailedVersion.filter(_ != version).fold("")(" (" + _ + ")")
s"""$fullRunnerName version: $version$detailedVersionOpt
|Scala version (default): ${ScalaCli.getDefaultScalaVersion}""".stripMargin
|Scala version (default): $defaultScalaVersion""".stripMargin
}
44 changes: 15 additions & 29 deletions modules/cli/src/main/scala/scala/cli/launcher/LauncherOptions.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package scala.cli.launcher

import caseapp.*
import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec
import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker

import scala.cli.commands.shared.HelpGroup
import scala.cli.commands.shared.{HelpGroup, SharedOptions}
import scala.cli.commands.{Constants, tags}

@HelpMessage("Run another Scala CLI version")
Expand All @@ -19,36 +21,20 @@ final case class LauncherOptions(
@Hidden
@Tag(tags.implementation)
cliScalaVersion: Option[String] = None,
@Group(HelpGroup.Launcher.toString)
@HelpMessage(
s"The default version of Scala used when processing user inputs (current default: ${Constants.defaultScalaVersion}). Can be overridden with --scala-version. "
)
@ValueDescription("version")
@Hidden
@Tag(tags.implementation)
@Name("cliDefaultScalaVersion")
cliUserScalaVersion: Option[String] = None,
@Group(HelpGroup.Launcher.toString)
@HelpMessage("")
@Hidden
@Tag(tags.implementation)
@Name("r")
@Name("repo")
@Name("repository")
@Name("predefinedRepository")
cliPredefinedRepository: List[String] = Nil,
@Group(HelpGroup.Launcher.toString)
@HelpMessage(
"This allows to override the program name identified by Scala CLI as itself (the default is 'scala-cli')"
)
@Hidden
@Tag(tags.implementation)
progName: Option[String] = None,
@Recurse
scalaRunner: ScalaRunnerLauncherOptions = ScalaRunnerLauncherOptions(),
@Recurse
powerOptions: PowerOptions = PowerOptions()
)
) {
def toCliArgs: List[String] =
cliVersion.toList.flatMap(v => List("--cli-version", v)) ++
cliScalaVersion.toList.flatMap(v => List("--cli-scala-version", v)) ++
scalaRunner.toCliArgs ++
powerOptions.toCliArgs
}

object LauncherOptions {
implicit lazy val parser: Parser[LauncherOptions] = Parser.derive
implicit lazy val help: Help[LauncherOptions] = Help.derive
implicit lazy val parser: Parser[LauncherOptions] = Parser.derive
implicit lazy val help: Help[LauncherOptions] = Help.derive
implicit lazy val jsonCodec: JsonValueCodec[LauncherOptions] = JsonCodecMaker.make
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ case class PowerOptions(
@HelpMessage("Allows to use restricted & experimental features")
@Tag(tags.must)
power: Boolean = false
)
) {
def toCliArgs: List[String] = if power then List("--power") else Nil
}

object PowerOptions {
implicit val parser: Parser[PowerOptions] = Parser.derive
Expand Down
Loading

0 comments on commit c04ef18

Please sign in to comment.