diff --git a/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala b/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala index a6b956fb93..7055e29e80 100644 --- a/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala +++ b/modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala @@ -8,7 +8,7 @@ import java.nio.file.Paths import scala.build.errors.Severity import scala.build.internal.WrapperParams -import scala.build.internal.util.ConsoleUtils.ScalaCliConsole +import scala.build.internals.ConsoleUtils.ScalaCliConsole import scala.build.options.Scope import scala.build.postprocessing.LineConversion.scalaLineToScLine import scala.collection.mutable diff --git a/modules/build/src/main/scala/scala/build/Directories.scala b/modules/build/src/main/scala/scala/build/Directories.scala index 308f47aff4..33b2618650 100644 --- a/modules/build/src/main/scala/scala/build/Directories.scala +++ b/modules/build/src/main/scala/scala/build/Directories.scala @@ -4,6 +4,7 @@ import coursier.cache.shaded.dirs.{GetWinDirs, ProjectDirectories} import scala.build.errors.ConfigDbException import scala.build.internal.JniGetWinDirs +import scala.build.internals.EnvVar import scala.cli.config.ConfigDb import scala.util.Properties @@ -19,7 +20,7 @@ trait Directories { def cacheDir: os.Path final def dbPath: os.Path = - Option(System.getenv("SCALA_CLI_CONFIG")) + EnvVar.ScalaCli.config.valueOpt .filter(_.trim.nonEmpty) .map(os.Path(_, os.pwd)) .getOrElse(secretsDir / Directories.defaultDbFileName) @@ -95,7 +96,7 @@ object Directories { SubDir(dir) lazy val directories: Directories = - Option(System.getenv("SCALA_CLI_HOME")).filter(_.trim.nonEmpty) match { + EnvVar.ScalaCli.home.valueOpt.filter(_.trim.nonEmpty) match { case None => scala.build.Directories.default() case Some(homeDir) => diff --git a/modules/build/src/main/scala/scala/build/internal/Runner.scala b/modules/build/src/main/scala/scala/build/internal/Runner.scala index 0d336a3e2d..f1a6f7a780 100644 --- a/modules/build/src/main/scala/scala/build/internal/Runner.scala +++ b/modules/build/src/main/scala/scala/build/internal/Runner.scala @@ -12,6 +12,7 @@ import java.nio.file.{Files, Path, Paths} import scala.build.EitherCps.{either, value} import scala.build.Logger import scala.build.errors._ +import scala.build.internals.EnvVar import scala.build.testrunner.{AsmTestRunner, TestRunner} import scala.util.{Failure, Properties, Success} @@ -191,12 +192,12 @@ object Runner { if (Paths.get(app).getNameCount >= 2) Some(asIs) else { def pathEntries = - Option(System.getenv("PATH")) + EnvVar.Misc.path.valueOpt .iterator .flatMap(_.split(File.pathSeparator).iterator) def pathSep = if (Properties.isWin) - Option(System.getenv("PATHEXT")) + EnvVar.Misc.pathExt.valueOpt .iterator .flatMap(_.split(File.pathSeparator).iterator) else Iterator("") diff --git a/modules/build/src/main/scala/scala/build/internal/zip/WrappedZipInputStream.scala b/modules/build/src/main/scala/scala/build/internal/zip/WrappedZipInputStream.scala index aa7fc9050e..c2f2b9e30c 100644 --- a/modules/build/src/main/scala/scala/build/internal/zip/WrappedZipInputStream.scala +++ b/modules/build/src/main/scala/scala/build/internal/zip/WrappedZipInputStream.scala @@ -3,6 +3,8 @@ package scala.build.internal.zip import java.io.{Closeable, InputStream} import java.util.zip.ZipEntry +import scala.build.internals.EnvVar + /* * juz.ZipInputStream is buggy on Arch Linux from native images (CRC32 calculation issues, * see oracle/graalvm#4479), so we use a custom ZipInputStream with disabled CRC32 calculation. @@ -35,7 +37,7 @@ object WrappedZipInputStream { case "true" | "1" => true case _ => false } - Option(System.getenv("SCALA_CLI_VENDORED_ZIS")).map(toBoolean) + EnvVar.ScalaCli.vendoredZipInputStream.valueOpt.map(toBoolean) .orElse(sys.props.get("scala-cli.zis.vendored").map(toBoolean)) .getOrElse(false) } diff --git a/modules/build/src/test/scala/scala/build/tests/util/BloopServer.scala b/modules/build/src/test/scala/scala/build/tests/util/BloopServer.scala index 5de48bdd51..2d54095115 100644 --- a/modules/build/src/test/scala/scala/build/tests/util/BloopServer.scala +++ b/modules/build/src/test/scala/scala/build/tests/util/BloopServer.scala @@ -3,6 +3,7 @@ package scala.build.tests.util import bloop.rifle.BloopRifleConfig import coursier.cache.FileCache +import scala.build.internals.EnvVar import scala.build.{Bloop, Logger} import scala.util.Properties @@ -30,7 +31,7 @@ object BloopServer { // which can have issues with the directory of "java" in the PATH, // if it contains '+' or '%' IIRC. // So we hardcode the path to "java" here. - Option(System.getenv("JAVA_HOME")) + EnvVar.Java.javaHome.valueOpt .map(os.Path(_, os.pwd)) .map(_ / "bin" / "java") .map(_.toString) diff --git a/modules/cli/src/main/scala/scala/cli/ScalaCli.scala b/modules/cli/src/main/scala/scala/cli/ScalaCli.scala index c81e31304d..0695a38459 100644 --- a/modules/cli/src/main/scala/scala/cli/ScalaCli.scala +++ b/modules/cli/src/main/scala/scala/cli/ScalaCli.scala @@ -11,6 +11,7 @@ import java.util.Locale import scala.build.Directories import scala.build.internal.Constants +import scala.build.internals.EnvVar import scala.cli.config.{ConfigDb, Keys} import scala.cli.internal.Argv0 import scala.cli.javaLauncher.JavaLauncherCli @@ -45,7 +46,7 @@ object ScalaCli { powerEntry <- configDb.get(Keys.power).toOption power <- powerEntry } yield power - val isPowerEnv = Option(System.getenv("SCALA_CLI_POWER")).flatMap(_.toBooleanOption) + val isPowerEnv = EnvVar.ScalaCli.power.valueOpt.flatMap(_.toBooleanOption) val isPower = isPowerEnv.orElse(isPowerConfigDb).getOrElse(false) !isPower } @@ -94,8 +95,8 @@ object ScalaCli { baos.toByteArray } - private def isCI = System.getenv("CI") != null - private def printStackTraces = Option(System.getenv("SCALA_CLI_PRINT_STACK_TRACES")) + private def isCI = EnvVar.Internal.ci.valueOpt.nonEmpty + private def printStackTraces = EnvVar.ScalaCli.printStackTraces.valueOpt .map(_.toLowerCase(Locale.ROOT)) .exists { case "true" | "1" => true @@ -157,7 +158,7 @@ object ScalaCli { // for https://github.com/VirtusLab/scala-cli/issues/828 System.err.println( s"""Running - | export SCALA_CLI_VENDORED_ZIS=true + | export ${EnvVar.ScalaCli.vendoredZipInputStream.name}=true |before running $fullRunnerName might fix the issue. |""".stripMargin ) @@ -209,7 +210,8 @@ object ScalaCli { } // load java properties from JAVA_OPTS and JDK_JAVA_OPTIONS environment variables - val javaOpts = sys.env.get("JAVA_OPTS").toSeq ++ sys.env.get("JDK_JAVA_OPTIONS").toSeq + val javaOpts: Seq[String] = + EnvVar.Java.javaOpts.valueOpt.toSeq ++ EnvVar.Java.jdkJavaOpts.valueOpt.toSeq val ignoredJavaOpts = javaOpts @@ -224,7 +226,8 @@ object ScalaCli { }.flatten if ignoredJavaOpts.nonEmpty then System.err.println( - s"Warning: Only java properties are supported in JAVA_OPTS and JDK_JAVA_OPTIONS environment variables. Other options are ignored: ${ignoredJavaOpts.mkString(", ")}" + s"Warning: Only java properties are supported in ${EnvVar.Java.javaOpts.name} and ${EnvVar + .Java.jdkJavaOpts.name} environment variables. Other options are ignored: ${ignoredJavaOpts.mkString(", ")}" ) } diff --git a/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala b/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala index 077dc23220..923f727c45 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala @@ -20,7 +20,7 @@ import scala.build.errors.BuildException import scala.build.input.{ScalaCliInvokeData, SubCommand} import scala.build.internal.util.WarningMessages import scala.build.internal.{Constants, Runner} -import scala.build.internals.FeatureType +import scala.build.internals.{EnvVar, FeatureType} import scala.build.options.{BuildOptions, ScalacOpt, Scope} import scala.build.{Artifacts, Directories, Logger, Positioned, ReplArtifacts} import scala.cli.commands.default.LegacyScalaOptions @@ -284,6 +284,11 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T], sys.exit(exitCode.orExit(logger)) } + private def maybePrintEnvsHelp(options: T): Unit = + if sharedOptions(options).exists(_.helpGroups.helpEnvs) then + println(EnvVar.helpMessage(isPower = allowRestrictedFeatures)) + sys.exit(0) + override def helpFormat: HelpFormat = ScalaCliHelp.helpFormat override val messages: Help[T] = @@ -378,6 +383,7 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T], maybePrintSimpleScalacOutput(options, bo) maybePrintToolsHelp(options, bo) } + maybePrintEnvsHelp(options) logger.flushExperimentalWarnings runCommand(options, remainingArgs, options.global.logging.logger) } diff --git a/modules/cli/src/main/scala/scala/cli/commands/WatchUtil.scala b/modules/cli/src/main/scala/scala/cli/commands/WatchUtil.scala index 0b7d37a7d4..4881c18b66 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/WatchUtil.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/WatchUtil.scala @@ -2,7 +2,7 @@ package scala.cli.commands import scala.annotation.tailrec import scala.build.internal.StdInConcurrentReader -import scala.build.internal.util.ConsoleUtils.ScalaCliConsole +import scala.build.internals.ConsoleUtils.ScalaCliConsole object WatchUtil { diff --git a/modules/cli/src/main/scala/scala/cli/commands/addpath/AddPath.scala b/modules/cli/src/main/scala/scala/cli/commands/addpath/AddPath.scala index 0a02a80dc5..4851dcbe40 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/addpath/AddPath.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/addpath/AddPath.scala @@ -6,6 +6,7 @@ import coursier.env.{EnvironmentUpdate, ProfileUpdater} import java.io.File import scala.build.Logger +import scala.build.internals.EnvVar import scala.cli.CurrentParams import scala.cli.commands.{CustomWindowsEnvVarUpdater, ScalaCommand} import scala.util.Properties @@ -16,7 +17,10 @@ object AddPath extends ScalaCommand[AddPathOptions] { override def runCommand(options: AddPathOptions, args: RemainingArgs, logger: Logger): Unit = { if args.all.isEmpty then logger.error("Nothing to do") else { - val update = EnvironmentUpdate(Nil, Seq("PATH" -> args.all.mkString(File.pathSeparator))) + val update = EnvironmentUpdate( + Nil, + Seq(EnvVar.Misc.path.name -> args.all.mkString(File.pathSeparator)) + ) val didUpdate = if (Properties.isWin) { val updater = CustomWindowsEnvVarUpdater().withUseJni(Some(coursier.paths.Util.useJni())) diff --git a/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala b/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala index 468c86d44c..1294fdce2b 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala @@ -9,6 +9,7 @@ import scala.build.* import scala.build.bsp.{BspReloadableOptions, BspThreads} import scala.build.errors.BuildException import scala.build.input.Inputs +import scala.build.internals.EnvVar import scala.build.options.{BuildOptions, Scope} import scala.cli.commands.ScalaCommand import scala.cli.commands.publish.ConfigUtil.* @@ -63,7 +64,7 @@ object Bsp extends ScalaCommand[BspOptions] { .flatMap(_.get(Keys.power).toOption) .flatten .getOrElse(false) - val envPowerMode = latestEnvs.get("SCALA_CLI_POWER").exists(_.toBoolean) + val envPowerMode = latestEnvs.get(EnvVar.ScalaCli.power.name).exists(_.toBoolean) val launcherPowerArg = latestLauncherOptions.powerOptions.power val subCommandPowerArg = latestSharedOptions.powerOptions.power val latestPowerMode = configPowerMode || launcherPowerArg || subCommandPowerArg || envPowerMode @@ -208,7 +209,7 @@ object Bsp extends ScalaCommand[BspOptions] { baseOptions.notForBloopOptions.addRunnerDependencyOpt.orElse(Some(false)) ) ) - val withEnvs = envs.get("JAVA_HOME") + val withEnvs = envs.get(EnvVar.Java.javaHome.name) .filter(_ => withDefaults.javaOptions.javaHomeOpt.isEmpty) .map(javaHome => withDefaults.copy(javaOptions = diff --git a/modules/cli/src/main/scala/scala/cli/commands/config/ConfigOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/config/ConfigOptions.scala index 833e392a93..c47008d64d 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/config/ConfigOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/config/ConfigOptions.scala @@ -2,7 +2,7 @@ package scala.cli.commands.config import caseapp.* -import scala.build.internal.util.ConsoleUtils.ScalaCliConsole +import scala.build.internals.ConsoleUtils.ScalaCliConsole import scala.cli.ScalaCli.{allowRestrictedFeatures, fullRunnerName, progName} import scala.cli.commands.pgp.PgpScalaSigningOptions import scala.cli.commands.shared.* diff --git a/modules/cli/src/main/scala/scala/cli/commands/github/LibSodiumJni.scala b/modules/cli/src/main/scala/scala/cli/commands/github/LibSodiumJni.scala index 99468e79bd..bad4e94e37 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/github/LibSodiumJni.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/github/LibSodiumJni.scala @@ -12,7 +12,8 @@ import scala.build.EitherCps.{either, value} import scala.build.Logger import scala.build.errors.BuildException import scala.build.internal.{Constants, FetchExternalBinary} -import scala.cli.internal.{Constants => CliConstants} +import scala.build.internals.EnvVar +import scala.cli.internal.Constants as CliConstants import scala.util.Properties import scala.util.control.NonFatal @@ -135,7 +136,7 @@ object LibSodiumJni { case Some(sodiumLib) => System.load(sodiumLib.toString) case None => - val allow = Option(System.getenv("SCALA_CLI_SODIUM_JNI_ALLOW")) + val allow = EnvVar.ScalaCli.allowSodiumJni.valueOpt .map(_.toLowerCase(Locale.ROOT)) .forall { case "false" | "0" => false diff --git a/modules/cli/src/main/scala/scala/cli/commands/installcompletions/InstallCompletions.scala b/modules/cli/src/main/scala/scala/cli/commands/installcompletions/InstallCompletions.scala index 079178210c..2b01f9c82a 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/installcompletions/InstallCompletions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/installcompletions/InstallCompletions.scala @@ -9,6 +9,7 @@ import java.nio.charset.Charset import java.nio.file.Paths import java.util +import scala.build.internals.EnvVar import scala.build.{Directories, Logger} import scala.cli.commands.shared.HelpGroup import scala.cli.commands.{ScalaCommand, SpecificationLevel} @@ -57,7 +58,7 @@ object InstallCompletions extends ScalaCommand[InstallCompletionsOptions] { (script, defaultRcFile) case Zsh.id | "zsh" => val completionScript = Zsh.script(name) - val zDotDir = Option(System.getenv("ZDOTDIR")) + val zDotDir = EnvVar.Misc.zDotDir.valueOpt .map(os.Path(_, os.pwd)) .getOrElse(os.home) val defaultRcFile = zDotDir / ".zshrc" @@ -114,7 +115,7 @@ object InstallCompletions extends ScalaCommand[InstallCompletionsOptions] { def getFormat(format: Option[String]): Option[String] = format.map(_.trim).filter(_.nonEmpty) .orElse { - Option(System.getenv("SHELL")).map(_.split("[\\/]+").last).map { + EnvVar.Misc.shell.valueOpt.map(_.split("[\\/]+").last).map { case "bash" => Bash.id case "zsh" => Zsh.id case other => other diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/PublishParamsOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/PublishParamsOptions.scala index eb11a23a11..38d9a74755 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/publish/PublishParamsOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/PublishParamsOptions.scala @@ -2,6 +2,7 @@ package scala.cli.commands.publish import caseapp.* +import scala.build.internals.EnvVar import scala.cli.commands.shared.{HelpGroup, SharedVersionOptions} import scala.cli.commands.tags import scala.cli.signing.shared.PasswordOption @@ -77,10 +78,8 @@ final case class PublishParamsOptions( ) { // format: on - def setupCi: Boolean = - ci.getOrElse(false) - def isCi: Boolean = - ci.getOrElse(System.getenv("CI") != null) + def setupCi: Boolean = ci.getOrElse(false) + def isCi: Boolean = ci.getOrElse(EnvVar.Internal.ci.valueOpt.nonEmpty) } object PublishParamsOptions { diff --git a/modules/cli/src/main/scala/scala/cli/commands/run/Run.scala b/modules/cli/src/main/scala/scala/cli/commands/run/Run.scala index 7a55522632..881526a43f 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/run/Run.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/run/Run.scala @@ -13,8 +13,9 @@ import scala.build.EitherCps.{either, value} import scala.build.* import scala.build.errors.BuildException import scala.build.input.{Inputs, ScalaCliInvokeData, SubCommand} -import scala.build.internal.util.ConsoleUtils.ScalaCliConsole import scala.build.internal.{Constants, Runner, ScalaJsLinkerConfig} +import scala.build.internals.ConsoleUtils.ScalaCliConsole +import scala.build.internals.EnvVar import scala.build.options.{BuildOptions, JavaOpt, PackageType, Platform, ScalacOpt} import scala.cli.CurrentParams import scala.cli.commands.package0.Package @@ -498,9 +499,9 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers { if (pythonLibraryPaths.isEmpty) Map.empty else { val prependTo = - if (Properties.isWin) "PATH" - else if (Properties.isMac) "DYLD_LIBRARY_PATH" - else "LD_LIBRARY_PATH" + if (Properties.isWin) EnvVar.Misc.path.name + else if (Properties.isMac) EnvVar.Misc.dyldLibraryPath.name + else EnvVar.Misc.ldLibraryPath.name val currentOpt = Option(System.getenv(prependTo)) val currentEntries = currentOpt .map(_.split(File.pathSeparator).toSet) diff --git a/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala b/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala index 0774b018af..66ce4f4988 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala @@ -14,6 +14,7 @@ import scala.build.bsp.IdeInputs import scala.build.errors.{BuildException, WorkspaceError} import scala.build.input.{Inputs, OnDisk, Virtual, WorkspaceOrigin} import scala.build.internal.Constants +import scala.build.internals.EnvVar import scala.build.options.{BuildOptions, Scope} import scala.cli.CurrentParams import scala.cli.commands.shared.{SharedBspFileOptions, SharedOptions} @@ -185,7 +186,8 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] { val scalaCliOptionsForBspJson = writeToArray(options.shared)(SharedOptions.jsonCodec) val scalaCliLaunchOptsForBspJson = writeToArray(launcherOptions)(LauncherOptions.jsonCodec) val scalaCliBspInputsJson = writeToArray(ideInputs) - val scalaCliBspEnvsJson = writeToArray(sys.env) + val envsForBsp = sys.env.filter((key, _) => EnvVar.allBsp.map(_.name).contains(key)) + val scalaCliBspEnvsJson = writeToArray(envsForBsp) if (inputs.workspaceOrigin.contains(WorkspaceOrigin.HomeDir)) value(Left(new WorkspaceError( diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/CoursierOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/CoursierOptions.scala index d84c400f62..18c17ca1b5 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/CoursierOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/CoursierOptions.scala @@ -5,6 +5,7 @@ import com.github.plokhotnyuk.jsoniter_scala.core.* import com.github.plokhotnyuk.jsoniter_scala.macros.* import coursier.cache.{CacheLogger, CachePolicy, FileCache} +import scala.build.internals.EnvVar import scala.cli.commands.tags import scala.concurrent.duration.Duration @@ -54,7 +55,7 @@ final case class CoursierOptions( } def getOffline(): Option[Boolean] = offline - .orElse(Option(System.getenv("COURSIER_MODE")).map(_ == "offline")) + .orElse(EnvVar.Coursier.coursierMode.valueOpt.map(_ == "offline")) .orElse(Option(System.getProperty("coursier.mode")).map(_ == "offline")) } diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/HelpGroupOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/HelpGroupOptions.scala index 43ffaf74a3..16f5d84b4b 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/HelpGroupOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/HelpGroupOptions.scala @@ -10,6 +10,14 @@ import scala.cli.commands.tags @HelpMessage("Print help message") case class HelpGroupOptions( + @Group(HelpGroup.Help.toString) + @HelpMessage("Show environment variable help") + @Tag(tags.implementation) + @Tag(tags.inShortHelp) + @Name("helpEnv") + @Name("envHelp") + @Name("envsHelp") + helpEnvs: Boolean = false, @Group(HelpGroup.Help.toString) @HelpMessage("Show options for ScalaJS") @Tag(tags.implementation) diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala index f872be3b29..830c8e382c 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala @@ -26,9 +26,9 @@ import scala.build.errors.{AmbiguousPlatformError, BuildException, ConfigDbExcep import scala.build.input.{Element, Inputs, ResourceDirectory, ScalaCliInvokeData} import scala.build.interactive.Interactive import scala.build.interactive.Interactive.{InteractiveAsk, InteractiveNop} -import scala.build.internal.util.ConsoleUtils.ScalaCliConsole import scala.build.internal.util.WarningMessages import scala.build.internal.{Constants, FetchExternalBinary, OsLibc, Util} +import scala.build.internals.ConsoleUtils.ScalaCliConsole import scala.build.options.ScalaVersionUtil.fileWithTtl0 import scala.build.options.{BuildOptions, ComputeVersion, Platform, ScalacOpt, ShadowingSeq} import scala.build.preprocessing.directives.ClasspathUtils.* diff --git a/modules/cli/src/main/scala/scala/cli/commands/shebang/ShebangOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shebang/ShebangOptions.scala index 0793e35c27..7f1421297d 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shebang/ShebangOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shebang/ShebangOptions.scala @@ -2,7 +2,7 @@ package scala.cli.commands.shebang import caseapp.* -import scala.build.internal.util.ConsoleUtils.ScalaCliConsole +import scala.build.internals.ConsoleUtils.ScalaCliConsole import scala.cli.ScalaCli.{baseRunnerName, fullRunnerName, progName} import scala.cli.commands.run.RunOptions import scala.cli.commands.shared.{HasSharedOptions, HelpMessages, SharedOptions} diff --git a/modules/cli/src/main/scala/scala/cli/commands/test/Test.scala b/modules/cli/src/main/scala/scala/cli/commands/test/Test.scala index 790e3653b9..5592698087 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/test/Test.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/test/Test.scala @@ -9,8 +9,8 @@ import scala.build.EitherCps.{either, value} import scala.build.Ops.* import scala.build.* import scala.build.errors.{BuildException, CompositeBuildException} -import scala.build.internal.util.ConsoleUtils.ScalaCliConsole import scala.build.internal.{Constants, Runner} +import scala.build.internals.ConsoleUtils.ScalaCliConsole import scala.build.options.{BuildOptions, JavaOpt, Platform, Scope} import scala.build.testrunner.AsmTestRunner import scala.cli.CurrentParams diff --git a/modules/cli/src/main/scala/scala/cli/commands/uninstallcompletions/UninstallCompletions.scala b/modules/cli/src/main/scala/scala/cli/commands/uninstallcompletions/UninstallCompletions.scala index 16574a3e93..d3f701c83b 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/uninstallcompletions/UninstallCompletions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/uninstallcompletions/UninstallCompletions.scala @@ -6,6 +6,7 @@ import caseapp.core.help.HelpFormat import java.nio.charset.Charset import scala.build.Logger +import scala.build.internals.EnvVar import scala.cli.CurrentParams import scala.cli.commands.installcompletions.InstallCompletions import scala.cli.commands.shared.HelpGroup @@ -31,7 +32,7 @@ object UninstallCompletions extends ScalaCommand[UninstallCompletionsOptions] { ): Unit = { val name = InstallCompletions.getName(options.shared.name) - val zDotDir = Option(System.getenv("ZDOTDIR")) + val zDotDir = EnvVar.Misc.zDotDir.valueOpt .map(os.Path(_, os.pwd)) .getOrElse(os.home) val rcFiles = options.shared.rcFile.map(file => Seq(os.Path(file, os.pwd))).getOrElse(Seq( diff --git a/modules/cli/src/main/scala/scala/cli/commands/util/RunSpark.scala b/modules/cli/src/main/scala/scala/cli/commands/util/RunSpark.scala index 165211566c..0356661da3 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/util/RunSpark.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/util/RunSpark.scala @@ -3,8 +3,9 @@ package scala.cli.commands.util import scala.build.EitherCps.{either, value} import scala.build.errors.BuildException import scala.build.internal.Runner +import scala.build.internals.EnvVar import scala.build.{Build, Logger} -import scala.cli.commands.package0.{Package => PackageCmd} +import scala.cli.commands.package0.Package as PackageCmd import scala.cli.commands.packaging.Spark import scala.cli.commands.run.RunMode import scala.cli.packaging.Library @@ -32,7 +33,7 @@ object RunSpark { val javaOpts = build.options.javaOptions.javaOpts.toSeq.map(_.value.value) val ext = if (Properties.isWin) ".cmd" else "" val submitCommand: String = - Option(System.getenv("SPARK_HOME")) + EnvVar.Spark.sparkHome.valueOpt .map(os.Path(_, os.pwd)) .map(_ / "bin" / s"spark-submit$ext") .filter(os.exists(_)) diff --git a/modules/cli/src/main/scala/scala/cli/internal/ProcUtil.scala b/modules/cli/src/main/scala/scala/cli/internal/ProcUtil.scala index f7acdafc7a..1facca8b46 100644 --- a/modules/cli/src/main/scala/scala/cli/internal/ProcUtil.scala +++ b/modules/cli/src/main/scala/scala/cli/internal/ProcUtil.scala @@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets import java.util.concurrent.{CancellationException, CompletableFuture, CompletionException} import scala.build.Logger +import scala.build.internals.EnvVar import scala.util.control.NonFatal import scala.util.{Properties, Try} @@ -73,8 +74,8 @@ object ProcUtil { def findApplicationPathsOnPATH(appName: String): List[String] = { import java.io.File.pathSeparator, java.io.File.pathSeparatorChar - var path = System.getenv("PATH") - val pwd = os.pwd.toString + var path: String = EnvVar.Misc.path.valueOpt.getOrElse("") + val pwd: String = os.pwd.toString // on unix & macs, an empty PATH counts as ".", the working directory if (path.length == 0) @@ -113,7 +114,7 @@ object ProcUtil { "/bin/fish" ) - def isShebangCapableShell = Option(System.getenv("SHELL")) match + def isShebangCapableShell = EnvVar.Misc.shell.valueOpt match case Some(currentShell) if shebangCapableShells.exists(sh => currentShell.contains(sh)) => true case _ => false } diff --git a/modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala b/modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala index e431b0ec75..1f2b7572b2 100644 --- a/modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala +++ b/modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala @@ -4,6 +4,7 @@ import java.io.File import scala.annotation.tailrec import scala.build.internal.{ManifestJar, Runner} +import scala.build.internals.EnvVar import scala.build.{Build, Logger, Positioned} import scala.cli.errors.GraalVMNativeImageError import scala.cli.graal.{BytecodeProcessor, TempCache} @@ -37,19 +38,20 @@ object NativeImage { private def vcVersions = Seq("2022", "2019", "2017") private def vcEditions = Seq("Enterprise", "Community", "BuildTools") - lazy val vcvarsCandidates = Option(System.getenv("VCVARSALL")) ++ { - for { - isX86 <- Seq(false, true) - version <- vcVersions - edition <- vcEditions - } yield { - val programFiles = if (isX86) "Program Files (x86)" else "Program Files" - """C:\""" + programFiles + """\Microsoft Visual Studio\""" + version + "\\" + edition + """\VC\Auxiliary\Build\vcvars64.bat""" + private lazy val vcVarsCandidates: Iterable[String] = + EnvVar.Misc.vcVarsAll.valueOpt ++ { + for { + isX86 <- Seq(false, true) + version <- vcVersions + edition <- vcEditions + } yield { + val programFiles = if (isX86) "Program Files (x86)" else "Program Files" + """C:\""" + programFiles + """\Microsoft Visual Studio\""" + version + "\\" + edition + """\VC\Auxiliary\Build\vcvars64.bat""" + } } - } private def vcvarsOpt: Option[os.Path] = - vcvarsCandidates + vcVarsCandidates .iterator .map(os.Path(_, os.pwd)) .filter(os.exists(_)) diff --git a/modules/build/src/main/scala/scala/build/internal/util/ConsoleUtils.scala b/modules/core/src/main/scala/scala/build/internals/ConsoleUtils.scala similarity index 95% rename from modules/build/src/main/scala/scala/build/internal/util/ConsoleUtils.scala rename to modules/core/src/main/scala/scala/build/internals/ConsoleUtils.scala index 4faad5ef98..65d670bef0 100644 --- a/modules/build/src/main/scala/scala/build/internal/util/ConsoleUtils.scala +++ b/modules/core/src/main/scala/scala/build/internals/ConsoleUtils.scala @@ -1,4 +1,4 @@ -package scala.build.internal.util +package scala.build.internals object ConsoleUtils { import Console.* diff --git a/modules/core/src/main/scala/scala/build/internals/EnvVar.scala b/modules/core/src/main/scala/scala/build/internals/EnvVar.scala new file mode 100644 index 0000000000..a206c42b82 --- /dev/null +++ b/modules/core/src/main/scala/scala/build/internals/EnvVar.scala @@ -0,0 +1,132 @@ +package scala.build.internals + +import scala.build.internals.ConsoleUtils.ScalaCliConsole + +/** @param name + * The name of the environment variable + * @param description + * A short description what is it used for + * @param passToIde + * Whether to pass this variable to the IDE/BSP client (true by default, should only be disabled + * for env vars which aren't safe to save on disk) + * @param requiresPower + * Whether this variable is related to a feature that requires power mode; also used for internal + * toggles and such + */ +case class EnvVar( + name: String, + description: String, + passToIde: Boolean = true, + requiresPower: Boolean = false +) { + def valueOpt: Option[String] = Option(System.getenv(name)) + override def toString: String = s"$name=${valueOpt.getOrElse("")}" + def helpMessage(spaces: String): String = { + val powerString = + if requiresPower then s"${ScalaCliConsole.GRAY}(power)${Console.RESET} " else "" + s"${Console.YELLOW}$name${Console.RESET}$spaces$powerString$description" + } +} +object EnvVar { + def helpMessage(isPower: Boolean): String = + s"""The following is the list of environment variables used and recognized by Scala CLI. + |It should by no means be treated as an exhaustive list + |Some tools and libraries Scala CLI integrates with may have their own, which may or may not be listed here. + |${if isPower then "" else "For the expanded list, pass --power." + System.lineSeparator()} + |${ + val maxFullNameLength = + EnvVar.all.filter(!_.requiresPower || isPower).map(_.name.length).max + EnvVar.allGroups + .map(_.subsectionMessage(maxFullNameLength, isPower)) + .filter(_.linesIterator.size > 1) + .mkString(s"${System.lineSeparator() * 2}") + }""".stripMargin + + trait EnvVarGroup { + def all: Seq[EnvVar] + def groupName: String + def subsectionMessage(maxFullNameLength: Int, isPower: Boolean): String = { + val envsToInclude = all.filter(!_.requiresPower || isPower) + s"""$groupName + |${ + envsToInclude + .map(ev => + s" ${ev.helpMessage(spaces = " " * (maxFullNameLength - ev.name.length + 2))}" + ) + .mkString(System.lineSeparator()) + }""".stripMargin + } + } + def allGroups: Seq[EnvVarGroup] = Seq(ScalaCli, Java, Coursier, Spark, Misc, Internal) + def all: Seq[EnvVar] = allGroups.flatMap(_.all) + def allBsp: Seq[EnvVar] = all.filter(_.passToIde) + object Java extends EnvVarGroup { + override def groupName: String = "Java" + override def all = Seq(javaHome, javaOpts, jdkJavaOpts) + val javaHome = EnvVar("JAVA_HOME", "Java installation directory") + val javaOpts = EnvVar("JAVA_OPTS", "Java options") + val jdkJavaOpts = EnvVar("JDK_JAVA_OPTIONS", "JDK Java options") + } + + object Misc extends EnvVarGroup { + override def groupName: String = "Miscellaneous" + override def all = Seq( + path, + dyldLibraryPath, + ldLibraryPath, + pathExt, + shell, + vcVarsAll, + zDotDir + ) + val path = EnvVar("PATH", "The app path variable") + val dyldLibraryPath = EnvVar("DYLD_LIBRARY_PATH", "Runtime library paths on Mac OS X") + val ldLibraryPath = EnvVar("LD_LIBRARY_PATH", "Runtime library paths on Linux") + val pathExt = EnvVar("PATHEXT", "Executable file extensions on Windows") + val shell = EnvVar("SHELL", "The currently used shell") + val vcVarsAll = EnvVar("VCVARSALL", "Visual C++ Redistributable Runtimes") + val zDotDir = EnvVar("ZDOTDIR", "Zsh configuration directory") + } + + object Coursier extends EnvVarGroup { + override def groupName: String = "Coursier" + override def all = Seq(coursierCache, coursierMode) + val coursierCache = EnvVar("COURSIER_CACHE", "Coursier cache location") + val coursierMode = EnvVar("COURSIER_MODE", "Coursier mode (can be set to 'offline')") + } + + object ScalaCli extends EnvVarGroup { + override def groupName: String = "Scala CLI" + def all = Seq( + config, + home, + interactive, + interactiveInputs, + power, + printStackTraces, + allowSodiumJni, + vendoredZipInputStream + ) + val config = EnvVar("SCALA_CLI_CONFIG", "Scala CLI configuration file path") + val home = EnvVar("SCALA_CLI_HOME", "Scala CLI home directory") + val interactive = EnvVar("SCALA_CLI_INTERACTIVE", "Interactive mode toggle") + val interactiveInputs = EnvVar("SCALA_CLI_INTERACTIVE_INPUTS", "Interactive mode inputs") + val power = EnvVar("SCALA_CLI_POWER", "Power mode toggle") + val printStackTraces = EnvVar("SCALA_CLI_PRINT_STACK_TRACES", "Print stack traces toggle") + val allowSodiumJni = EnvVar("SCALA_CLI_SODIUM_JNI_ALLOW", "Allow to load libsodiumjni") + val vendoredZipInputStream = + EnvVar("SCALA_CLI_VENDORED_ZIS", "Toggle io.github.scala_cli.zip.ZipInputStream") + } + + object Spark extends EnvVarGroup { + override def groupName: String = "Spark" + override def all = Seq(sparkHome) + val sparkHome = EnvVar("SPARK_HOME", "Spark installation directory", requiresPower = true) + } + + object Internal extends EnvVarGroup { + override def groupName: String = "Internal" + def all = Seq(ci) + val ci = EnvVar("CI", "Marker for running on the CI", requiresPower = true) + } +} diff --git a/modules/generate-reference-doc/src/main/scala/scala/cli/doc/ReferenceDocUtils.scala b/modules/generate-reference-doc/src/main/scala/scala/cli/doc/ReferenceDocUtils.scala index 045fceca6c..2c52641a46 100644 --- a/modules/generate-reference-doc/src/main/scala/scala/cli/doc/ReferenceDocUtils.scala +++ b/modules/generate-reference-doc/src/main/scala/scala/cli/doc/ReferenceDocUtils.scala @@ -5,7 +5,7 @@ import caseapp.HelpMessage import java.util.stream.IntStream import scala.annotation.tailrec -import scala.build.internal.util.ConsoleUtils.* +import scala.build.internals.ConsoleUtils.* object ReferenceDocUtils { extension (s: String) { diff --git a/modules/integration/src/test/scala/scala/cli/integration/HelpTests.scala b/modules/integration/src/test/scala/scala/cli/integration/HelpTests.scala index 35c8e52066..6f667b80b5 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/HelpTests.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/HelpTests.scala @@ -72,6 +72,15 @@ class HelpTests extends ScalaCliSuite { "-cp, --jar, --jars, --class, --classes, -classpath, --extra-jar, --classpath, --extra-jars, --class-path, --extra-class, --extra-classes, --extra-class-path paths" )) } + for (withPower <- Seq(true, false)) + test("envs help" + (if (withPower) " with power" else "")) { + val powerOptions = if (withPower) Seq("--power") else Nil + val help = os.proc(TestUtil.cli, "--envs-help", powerOptions).call() + val helpOutput = help.out.trim() + if (!withPower) expect(!helpOutput.contains("(power)")) + expect(helpOutput.nonEmpty) + expect(helpOutput.contains("environment variables")) + } } object HelpTests { diff --git a/modules/options/src/main/scala/scala/build/interactive/Interactive.scala b/modules/options/src/main/scala/scala/build/interactive/Interactive.scala index a270e41fbd..215ada3a77 100644 --- a/modules/options/src/main/scala/scala/build/interactive/Interactive.scala +++ b/modules/options/src/main/scala/scala/build/interactive/Interactive.scala @@ -1,6 +1,7 @@ package scala.build.interactive import scala.build.internal.StdInConcurrentReader +import scala.build.internals.EnvVar sealed abstract class Interactive extends Product with Serializable { def confirmOperation(msg: String): Option[Boolean] = None @@ -10,8 +11,7 @@ sealed abstract class Interactive extends Product with Serializable { object Interactive { private var interactiveInputsOpt = - Option(System.getenv("SCALA_CLI_INTERACTIVE_INPUTS")) - .map(_.linesIterator.toList) + EnvVar.ScalaCli.interactiveInputs.valueOpt.map(_.linesIterator.toList) private def readLine(): String = interactiveInputsOpt match { @@ -39,7 +39,7 @@ object Interactive { if ( interactiveInputsOpt.nonEmpty || coursier.paths.Util.useAnsiOutput() || - System.getenv("SCALA_CLI_INTERACTIVE") != null + EnvVar.ScalaCli.interactive.valueOpt.nonEmpty ) action else None diff --git a/modules/options/src/main/scala/scala/build/options/BuildOptions.scala b/modules/options/src/main/scala/scala/build/options/BuildOptions.scala index 136fb5746d..de105bb4d1 100644 --- a/modules/options/src/main/scala/scala/build/options/BuildOptions.scala +++ b/modules/options/src/main/scala/scala/build/options/BuildOptions.scala @@ -21,6 +21,7 @@ import scala.build.internal.Constants.* import scala.build.internal.CsLoggerUtil.* import scala.build.internal.Regexes.scala3NightlyNicknameRegex import scala.build.internal.{Constants, OsLibc, StableScalaVersion, Util} +import scala.build.internals.EnvVar import scala.build.options.BuildRequirements.ScopeRequirement import scala.build.options.validation.BuildOptionsRule import scala.build.{Artifacts, Logger, Os, Position, Positioned} @@ -285,7 +286,7 @@ final case class BuildOptions( lazy val scalaParams: Either[BuildException, Option[ScalaParameters]] = either { val params = - if (System.getenv("CI") == null) + if EnvVar.Internal.ci.valueOpt.isEmpty then computeScalaParams(Constants.version, finalCache, value(finalRepositories)).orElse( // when the passed scala version is missed in the cache, we always force a cache refresh // https://github.com/VirtusLab/scala-cli/issues/1090 @@ -595,8 +596,8 @@ object BuildOptions { currentEnv.keys.find(_.equalsIgnoreCase(name)).getOrElse(name) else name - val javaHomeKey = keyFor("JAVA_HOME") - val pathKey = keyFor("PATH") + val javaHomeKey = keyFor(EnvVar.Java.javaHome.name) + val pathKey = keyFor(EnvVar.Misc.path.name) val updatedPath = { val valueOpt = currentEnv.get(pathKey) val entry = (javaHome / "bin").toString diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 3e8ec72231..31900e7e2b 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -495,6 +495,12 @@ Available in commands: +### `--help-envs` + +Aliases: `--env-help`, `--envs-help`, `--help-env` + +Show environment variable help + ### `--help-js` Show options for ScalaJS diff --git a/website/docs/reference/scala-command/cli-options.md b/website/docs/reference/scala-command/cli-options.md index 4ebe7de43d..c00d51d3b2 100644 --- a/website/docs/reference/scala-command/cli-options.md +++ b/website/docs/reference/scala-command/cli-options.md @@ -426,6 +426,14 @@ Available in commands: +### `--help-envs` + +Aliases: `--env-help`, `--envs-help`, `--help-env` + +`IMPLEMENTATION specific` per Scala Runner specification + +Show environment variable help + ### `--help-js` `IMPLEMENTATION specific` per Scala Runner specification diff --git a/website/docs/reference/scala-command/runner-specification.md b/website/docs/reference/scala-command/runner-specification.md index e9174d71d2..85e58f1468 100644 --- a/website/docs/reference/scala-command/runner-specification.md +++ b/website/docs/reference/scala-command/runner-specification.md @@ -586,6 +586,12 @@ Should include Scala CLI runner on the runtime ClassPath. Runner is added by def +**--help-envs** + +Show environment variable help + +Aliases: `--help-env` ,`--env-help` ,`--envs-help` + **--help-js** Show options for ScalaJS @@ -1339,6 +1345,12 @@ Should include Scala CLI runner on the runtime ClassPath. Runner is added by def +**--help-envs** + +Show environment variable help + +Aliases: `--help-env` ,`--env-help` ,`--envs-help` + **--help-js** Show options for ScalaJS @@ -1924,6 +1936,12 @@ Should include Scala CLI runner on the runtime ClassPath. Runner is added by def +**--help-envs** + +Show environment variable help + +Aliases: `--help-env` ,`--env-help` ,`--envs-help` + **--help-js** Show options for ScalaJS @@ -2533,6 +2551,12 @@ Should include Scala CLI runner on the runtime ClassPath. Runner is added by def +**--help-envs** + +Show environment variable help + +Aliases: `--help-env` ,`--env-help` ,`--envs-help` + **--help-js** Show options for ScalaJS @@ -3151,6 +3175,12 @@ Should include Scala CLI runner on the runtime ClassPath. Runner is added by def +**--help-envs** + +Show environment variable help + +Aliases: `--help-env` ,`--env-help` ,`--envs-help` + **--help-js** Show options for ScalaJS @@ -3727,6 +3757,12 @@ Should include Scala CLI runner on the runtime ClassPath. Runner is added by def +**--help-envs** + +Show environment variable help + +Aliases: `--help-env` ,`--env-help` ,`--envs-help` + **--help-js** Show options for ScalaJS @@ -4378,6 +4414,12 @@ Should include Scala CLI runner on the runtime ClassPath. Runner is added by def +**--help-envs** + +Show environment variable help + +Aliases: `--help-env` ,`--env-help` ,`--envs-help` + **--help-js** Show options for ScalaJS @@ -5036,6 +5078,12 @@ Should include Scala CLI runner on the runtime ClassPath. Runner is added by def +**--help-envs** + +Show environment variable help + +Aliases: `--help-env` ,`--env-help` ,`--envs-help` + **--help-js** Show options for ScalaJS @@ -5959,6 +6007,12 @@ Should include Scala CLI runner on the runtime ClassPath. Runner is added by def +**--help-envs** + +Show environment variable help + +Aliases: `--help-env` ,`--env-help` ,`--envs-help` + **--help-js** Show options for ScalaJS