Skip to content

Commit

Permalink
Ensure BSP respects --power mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Gedochao committed Jul 2, 2024
1 parent 0f7c5e8 commit 532ae6d
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 3 deletions.
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 @@ -49,7 +49,8 @@ object ScalaCli {
val isPower = isPowerEnv.orElse(isPowerConfigDb).getOrElse(false)
!isPower
}
def allowRestrictedFeatures = !isSipScala
def setPowerMode(power: Boolean): Unit = isSipScala = !power
def allowRestrictedFeatures = !isSipScala
def fullRunnerName =
if (progName.contains(scalaCliBinaryName)) "Scala CLI" else "Scala code runner"
def baseRunnerName = if (progName.contains(scalaCliBinaryName)) scalaCliBinaryName else "scala"
Expand Down
28 changes: 27 additions & 1 deletion modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import scala.build.bsp.{BspReloadableOptions, BspThreads}
import scala.build.errors.BuildException
import scala.build.input.Inputs
import scala.build.options.{BuildOptions, Scope}
import scala.cli.CurrentParams
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.cli.util.ConfigDbUtils
import scala.cli.{CurrentParams, ScalaCli}
import scala.concurrent.Await
import scala.concurrent.duration.Duration

Expand All @@ -30,6 +31,7 @@ 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))
Expand All @@ -51,6 +53,24 @@ object Bsp extends ScalaCommand[BspOptions] {
override def sharedOptions(options: BspOptions): Option[SharedOptions] =
Option(latestSharedOptions(options))

private def refreshPowerMode(
latestLauncherOptions: LauncherOptions,
latestSharedOptions: SharedOptions,
latestEnvs: Map[String, String]
): Unit = {
val previousPowerMode = ScalaCli.allowRestrictedFeatures
val configPowerMode = ConfigDbUtils.getConfigDbOpt(latestSharedOptions.logger)
.flatMap(_.get(Keys.power).toOption)
.flatten
.getOrElse(false)
val envPowerMode = latestEnvs.get("SCALA_CLI_POWER").exists(_.toBoolean)
val launcherPowerArg = latestLauncherOptions.powerOptions.power
val subCommandPowerArg = latestSharedOptions.powerOptions.power
val latestPowerMode = configPowerMode || launcherPowerArg || subCommandPowerArg || envPowerMode
// only set power mode if it's been turned on since, never turn it off in BSP
if !previousPowerMode && latestPowerMode then ScalaCli.setPowerMode(latestPowerMode)
}

// not reusing buildOptions here, since they should be reloaded live instead
override def runCommand(options: BspOptions, args: RemainingArgs, logger: Logger): Unit = {
if (options.shared.logging.verbosity >= 3)
Expand All @@ -60,6 +80,8 @@ object Bsp extends ScalaCommand[BspOptions] {
val getLauncherOptions: () => LauncherOptions = () => latestLauncherOptions(options)
val getEnvsFromFile: () => Map[String, String] = () => latestEnvsFromFile(options)

refreshPowerMode(getLauncherOptions(), getSharedOptions(), getEnvsFromFile())

val preprocessInputs: Seq[String] => Either[BuildException, (Inputs, BuildOptions)] =
argsSeq =>
either {
Expand All @@ -68,6 +90,8 @@ object Bsp extends ScalaCommand[BspOptions] {
val envs = getEnvsFromFile()
val initialInputs = value(sharedOptions.inputs(argsSeq, () => Inputs.default()))

refreshPowerMode(launcherOptions, sharedOptions, envs)

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

Expand Down Expand Up @@ -114,6 +138,7 @@ object Bsp extends ScalaCommand[BspOptions] {
val envs = getEnvsFromFile()
val bspBuildOptions = buildOptions(sharedOptions, launcherOptions, envs)
.orElse(finalBuildOptions)
refreshPowerMode(launcherOptions, sharedOptions, envs)
BspReloadableOptions(
buildOptions = bspBuildOptions,
bloopRifleConfig = sharedOptions.bloopRifleConfig(Some(bspBuildOptions))
Expand All @@ -129,6 +154,7 @@ object Bsp extends ScalaCommand[BspOptions] {
val envs = getEnvsFromFile()
val bloopRifleConfig = sharedOptions.bloopRifleConfig(Some(finalBuildOptions))
.orExit(sharedOptions.logger)
refreshPowerMode(launcherOptions, sharedOptions, envs)

BspReloadableOptions(
buildOptions = buildOptions(sharedOptions, launcherOptions, envs),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
attempts: Int = if (TestUtil.isCI) 3 else 1,
pauseDuration: FiniteDuration = 5.seconds,
bspOptions: List[String] = List.empty,
bspEnvs: Map[String, String] = Map.empty,
reuseRoot: Option[os.Path] = None,
stdErrOpt: Option[os.RelPath] = None,
extraOptionsOverride: Seq[String] = extraOptions
Expand All @@ -96,7 +97,7 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
val stderr: os.ProcessOutput = stdErrPathOpt.getOrElse(os.Inherit)

val proc = os.proc(TestUtil.cli, "bsp", bspOptions ++ extraOptionsOverride, args)
.spawn(cwd = root, stderr = stderr)
.spawn(cwd = root, stderr = stderr, env = bspEnvs)
var remoteServer: b.BuildServer & b.ScalaBuildServer & b.JavaBuildServer & b.JvmBuildServer =
null

Expand Down Expand Up @@ -2107,6 +2108,135 @@ abstract class BspTestDefinitions extends ScalaCliSuite with TestScalaVersionArg
}
}

for {
setPowerByLauncherOpt <- Seq(true, false)
setPowerBySubCommandOpt <- Seq(true, false)
setPowerByEnv <- Seq(true, false)
setPowerByConfig <- Seq(true, false)
powerIsSet =
setPowerByLauncherOpt || setPowerBySubCommandOpt || setPowerByEnv || setPowerByConfig
powerSettingDescription = {
val launcherSetting = if (setPowerByLauncherOpt) "launcher option" else ""
val subCommandSetting = if (setPowerBySubCommandOpt) "setup-ide option" else ""
val envSetting = if (setPowerByEnv) "environment variable" else ""
val configSetting = if (setPowerByConfig) "config" else ""
List(launcherSetting, subCommandSetting, envSetting, configSetting)
.filter(_.nonEmpty)
.mkString(", ")
}
testDescription =
if (powerIsSet)
s"BSP respects --power mode set by $powerSettingDescription (example: using python directive)"
else
"BSP fails when --power mode is not set for experimental directives (example: using python directive)"
} test(testDescription) {
val scriptName = "requires-power.sc"
val inputs = TestInputs(os.rel / scriptName ->
s"""//> using python
|println("scalapy is experimental")""".stripMargin)
inputs.fromRoot { root =>
val configFile = os.rel / "config" / "config.json"
val configEnvs = Map("SCALA_CLI_CONFIG" -> configFile.toString())
val setupIdeEnvs: Map[String, String] =
if (setPowerByEnv) Map("SCALA_CLI_POWER" -> "true") ++ configEnvs
else configEnvs
val launcherOpts =
if (setPowerByLauncherOpt) List("--power")
else List.empty
val subCommandOpts =
if (setPowerBySubCommandOpt) List("--power")
else List.empty
val args = launcherOpts ++ List("setup-ide", scriptName) ++ subCommandOpts
os.proc(TestUtil.cli, args).call(cwd = root, env = setupIdeEnvs)
if (setPowerByConfig)
os.proc(TestUtil.cli, "config", "power", "true")
.call(cwd = root, env = configEnvs)
val ideOptionsPath = root / Constants.workspaceDirName / "ide-options-v2.json"
expect(ideOptionsPath.toNIO.toFile.exists())
val ideLauncherOptsPath = root / Constants.workspaceDirName / "ide-launcher-options.json"
expect(ideLauncherOptsPath.toNIO.toFile.exists())
val ideEnvsPath = root / Constants.workspaceDirName / "ide-envs.json"
expect(ideEnvsPath.toNIO.toFile.exists())
val jsonOptions = List(
"--json-options",
ideOptionsPath.toString,
"--json-launcher-options",
ideLauncherOptsPath.toString,
"--envs-file",
ideEnvsPath.toString
)
withBsp(
inputs,
Seq("."),
bspOptions = jsonOptions,
bspEnvs = configEnvs,
reuseRoot = Some(root)
) {
(_, _, remoteServer) =>
async {
val targets = await(remoteServer.workspaceBuildTargets().asScala)
.getTargets.asScala
.filter(!_.getId.getUri.contains("-test"))
.map(_.getId())
val compileResult =
await(remoteServer.buildTargetCompile(new b.CompileParams(targets.asJava)).asScala)
if (powerIsSet) {
expect(compileResult.getStatusCode == b.StatusCode.OK)
val runResult =
await(remoteServer.buildTargetRun(new b.RunParams(targets.head)).asScala)
expect(runResult.getStatusCode == b.StatusCode.OK)
}
else
expect(compileResult.getStatusCode == b.StatusCode.ERROR)
}
}
}
}

test("BSP reloads --power mode after setting it via env passed to setup-ide") {
val scriptName = "requires-power.sc"
val inputs = TestInputs(os.rel / scriptName ->
s"""//> using python
|println("scalapy is experimental")""".stripMargin)
inputs.fromRoot { root =>
os.proc(TestUtil.cli, "setup-ide", scriptName, extraOptions).call(cwd = root)
val ideEnvsPath = root / Constants.workspaceDirName / "ide-envs.json"
expect(ideEnvsPath.toNIO.toFile.exists())
val jsonOptions = List("--envs-file", ideEnvsPath.toString)
withBsp(inputs, Seq(scriptName), bspOptions = jsonOptions, reuseRoot = Some(root)) {
(_, _, remoteServer) =>
async {
val targets = await(remoteServer.workspaceBuildTargets().asScala)
.getTargets.asScala
.filter(!_.getId.getUri.contains("-test"))
.map(_.getId())

// compilation should fail before reload, as --power mode is off
val compileBeforeReloadResult =
await(remoteServer.buildTargetCompile(new b.CompileParams(targets.asJava)).asScala)
expect(compileBeforeReloadResult.getStatusCode == b.StatusCode.ERROR)

// enable --power mode via env for setup-ide
os.proc(TestUtil.cli, "setup-ide", scriptName, extraOptions)
.call(cwd = root, env = Map("SCALA_CLI_POWER" -> "true"))

// compilation should now succeed
val reloadResponse =
extractWorkspaceReloadResponse(await(remoteServer.workspaceReload().asScala))
expect(reloadResponse.isEmpty)
val compileAfterReloadResult =
await(remoteServer.buildTargetCompile(new b.CompileParams(targets.asJava)).asScala)
expect(compileAfterReloadResult.getStatusCode == b.StatusCode.OK)

// code should also be runnable via BSP now
val runResult =
await(remoteServer.buildTargetRun(new b.RunParams(targets.head)).asScala)
expect(runResult.getStatusCode == b.StatusCode.OK)
}
}
}
}

private def checkIfBloopProjectIsInitialised(
root: os.Path,
buildTargetsResp: b.WorkspaceBuildTargetsResult
Expand Down

0 comments on commit 532ae6d

Please sign in to comment.