
scala.cli.commands.shared.SharedOptions.scala Maven / Gradle / Ivy
package scala.cli.commands.shared
import bloop.rifle.BloopRifleConfig
import caseapp.*
import caseapp.core.help.Help
import com.github.plokhotnyuk.jsoniter_scala.core.*
import com.github.plokhotnyuk.jsoniter_scala.macros.*
import coursier.cache.FileCache
import coursier.util.{Artifact, Task}
import dependency.AnyDependency
import dependency.parser.DependencyParser
import java.io.{File, InputStream}
import java.nio.file.Paths
import java.util.concurrent.atomic.AtomicBoolean
import scala.build.EitherCps.{either, value}
import scala.build.Ops.EitherOptOps
import scala.build.*
import scala.build.compiler.{BloopCompilerMaker, ScalaCompilerMaker, SimpleScalaCompilerMaker}
import scala.build.directives.DirectiveDescription
import scala.build.errors.{AmbiguousPlatformError, BuildException, ConfigDbException, Severity}
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.options.ScalaVersionUtil.fileWithTtl0
import scala.build.options.{BuildOptions, ComputeVersion, Platform, ScalacOpt, ShadowingSeq}
import scala.build.preprocessing.directives.ClasspathUtils.*
import scala.build.preprocessing.directives.Toolkit
import scala.build.options as bo
import scala.cli.ScalaCli
import scala.cli.commands.publish.ConfigUtil.*
import scala.cli.commands.shared.{
HasGlobalOptions,
ScalaJsOptions,
ScalaNativeOptions,
SharedOptions,
SourceGeneratorOptions,
SuppressWarningOptions
}
import scala.cli.commands.tags
import scala.cli.commands.util.JvmUtils
import scala.cli.commands.util.ScalacOptionsUtil.*
import scala.cli.config.Key.BooleanEntry
import scala.cli.config.{ConfigDb, Keys}
import scala.cli.launcher.PowerOptions
import scala.cli.util.ConfigDbUtils
import scala.concurrent.ExecutionContextExecutorService
import scala.concurrent.duration.*
import scala.util.Properties
import scala.util.control.NonFatal
// format: off
final case class SharedOptions(
@Recurse
sharedVersionOptions: SharedVersionOptions = SharedVersionOptions(),
@Recurse
sourceGenerator: SourceGeneratorOptions = SourceGeneratorOptions(),
@Recurse
suppress: SuppressWarningOptions = SuppressWarningOptions(),
@Recurse
logging: LoggingOptions = LoggingOptions(),
@Recurse
powerOptions: PowerOptions = PowerOptions(),
@Recurse
js: ScalaJsOptions = ScalaJsOptions(),
@Recurse
native: ScalaNativeOptions = ScalaNativeOptions(),
@Recurse
compilationServer: SharedCompilationServerOptions = SharedCompilationServerOptions(),
@Recurse
dependencies: SharedDependencyOptions = SharedDependencyOptions(),
@Recurse
scalac: ScalacOptions = ScalacOptions(),
@Recurse
jvm: SharedJvmOptions = SharedJvmOptions(),
@Recurse
coursier: CoursierOptions = CoursierOptions(),
@Recurse
workspace: SharedWorkspaceOptions = SharedWorkspaceOptions(),
@Recurse
sharedPython: SharedPythonOptions = SharedPythonOptions(),
@Group(HelpGroup.Scala.toString)
@HelpMessage(s"Set the Scala version (${Constants.defaultScalaVersion} by default)")
@ValueDescription("version")
@Name("S")
@Name("scala")
@Tag(tags.must)
scalaVersion: Option[String] = None,
@Group(HelpGroup.Scala.toString)
@HelpMessage("Set the Scala binary version")
@ValueDescription("version")
@Hidden
@Name("B")
@Name("scalaBinary")
@Name("scalaBin")
@Tag(tags.must)
scalaBinaryVersion: Option[String] = None,
@Recurse
scalacExtra: ScalacExtraOptions = ScalacExtraOptions(),
@Recurse
snippet: SnippetOptions = SnippetOptions(),
@Recurse
markdown: MarkdownOptions = MarkdownOptions(),
@Group(HelpGroup.Java.toString)
@HelpMessage("Add extra JARs and compiled classes to the class path")
@ValueDescription("paths")
@Name("jar")
@Name("jars")
@Name("extraJar")
@Name("class")
@Name("extraClass")
@Name("classes")
@Name("extraClasses")
@Name("-classpath")
@Name("-cp")
@Name("classpath")
@Name("classPath")
@Name("extraClassPath")
@Tag(tags.must)
extraJars: List[String] = Nil,
@Group(HelpGroup.Java.toString)
@HelpMessage("Add extra JARs in the compilaion class path. Mainly using to run code in managed environments like Spark not to include certain depenencies on runtime ClassPath.")
@ValueDescription("paths")
@Name("compileOnlyJar")
@Name("compileOnlyJars")
@Name("extraCompileOnlyJar")
@Tag(tags.should)
extraCompileOnlyJars: List[String] = Nil,
@Group(HelpGroup.Java.toString)
@HelpMessage("Add extra source JARs")
@ValueDescription("paths")
@Name("sourceJar")
@Name("sourceJars")
@Name("extraSourceJar")
@Tag(tags.should)
extraSourceJars: List[String] = Nil,
@Group(HelpGroup.Java.toString)
@HelpMessage("Add a resource directory")
@ValueDescription("paths")
@Name("resourceDir")
@Tag(tags.must)
resourceDirs: List[String] = Nil,
@Hidden
@Group(HelpGroup.Java.toString)
@HelpMessage("Put project in class paths as a JAR rather than as a byte code directory")
@Tag(tags.experimental)
asJar: Boolean = false,
@Group(HelpGroup.Scala.toString)
@HelpMessage("Specify platform")
@ValueDescription("scala-js|scala-native|jvm")
@Tag(tags.should)
@Tag(tags.inShortHelp)
platform: Option[String] = None,
@Group(HelpGroup.Scala.toString)
@Tag(tags.implementation)
@Hidden
scalaLibrary: Option[Boolean] = None,
@Group(HelpGroup.Scala.toString)
@HelpMessage("Allows to include the Scala compiler artifacts on the classpath.")
@Tag(tags.must)
@Name("withScalaCompiler")
@Name("-with-compiler")
withCompiler: Option[Boolean] = None,
@Group(HelpGroup.Java.toString)
@HelpMessage("Do not add dependency to Scala Standard library. This is useful, when Scala CLI works with pure Java projects.")
@Tag(tags.implementation)
@Hidden
java: Option[Boolean] = None,
@Group(HelpGroup.Scala.toString)
@HelpMessage("Should include Scala CLI runner on the runtime ClassPath. Runner is added by default for application running on JVM using standard Scala versions. Runner is used to make stack traces more readable in case of application failure.")
@Tag(tags.implementation)
@Hidden
runner: Option[Boolean] = None,
@Hidden
@Tag(tags.should)
@HelpMessage("Generate SemanticDBs")
semanticDb: Option[Boolean] = None,
@Recurse
input: SharedInputOptions = SharedInputOptions(),
@Recurse
helpGroups: HelpGroupOptions = HelpGroupOptions(),
@Hidden
strictBloopJsonCheck: Option[Boolean] = None,
@Group(HelpGroup.Scala.toString)
@Name("d")
@Name("output-directory")
@Name("destination")
@Name("compileOutput")
@Name("compileOut")
@HelpMessage("Copy compilation results to output directory using either relative or absolute path")
@ValueDescription("/example/path")
@Tag(tags.must)
compilationOutput: Option[String] = None,
@Group(HelpGroup.Scala.toString)
@HelpMessage(s"Add toolkit to classPath (not supported in Scala 2.12), 'default' version for Scala toolkit: ${Constants.toolkitDefaultVersion}, 'default' version for typelevel toolkit: ${Constants.typelevelToolkitDefaultVersion}")
@ValueDescription("version|default")
@Name("toolkit")
@Tag(tags.implementation)
@Tag(tags.inShortHelp)
withToolkit: Option[String] = None,
@HelpMessage("Exclude sources")
exclude: List[String] = Nil,
@HelpMessage("Force object wrapper for scripts")
@Tag(tags.experimental)
objectWrapper: Option[Boolean] = None,
) extends HasGlobalOptions {
// format: on
def logger: Logger = logging.logger
override def global: GlobalOptions =
GlobalOptions(logging = logging, globalSuppress = suppress.global, powerOptions = powerOptions)
private def scalaJsOptions(opts: ScalaJsOptions): options.ScalaJsOptions = {
import opts._
options.ScalaJsOptions(
version = jsVersion,
mode = options.ScalaJsMode(jsMode),
moduleKindStr = jsModuleKind,
checkIr = jsCheckIr,
emitSourceMaps = jsEmitSourceMaps,
sourceMapsDest = jsSourceMapsPath.filter(_.trim.nonEmpty).map(os.Path(_, Os.pwd)),
dom = jsDom,
header = jsHeader,
allowBigIntsForLongs = jsAllowBigIntsForLongs,
avoidClasses = jsAvoidClasses,
avoidLetsAndConsts = jsAvoidLetsAndConsts,
moduleSplitStyleStr = jsModuleSplitStyle,
smallModuleForPackage = jsSmallModuleForPackage,
esVersionStr = jsEsVersion,
noOpt = jsNoOpt
)
}
private def linkerOptions(opts: ScalaJsOptions): options.scalajs.ScalaJsLinkerOptions = {
import opts._
options.scalajs.ScalaJsLinkerOptions(
linkerPath = jsLinkerPath
.filter(_.trim.nonEmpty)
.map(os.Path(_, Os.pwd)),
scalaJsVersion = jsVersion.map(_.trim).filter(_.nonEmpty),
scalaJsCliVersion = jsCliVersion.map(_.trim).filter(_.nonEmpty),
javaArgs = jsCliJavaArg,
useJvm = jsCliOnJvm.map {
case false => Left(FetchExternalBinary.platformSuffix())
case true => Right(())
}
)
}
private def scalaNativeOptions(opts: ScalaNativeOptions): options.ScalaNativeOptions = {
import opts._
options.ScalaNativeOptions(
nativeVersion,
nativeMode,
nativeLto,
nativeGc,
nativeClang,
nativeClangpp,
nativeLinking,
nativeLinkingDefaults,
nativeCompile,
nativeCompileDefaults,
embedResources,
nativeTarget
)
}
def buildOptions(
enableJmh: Boolean = false,
jmhVersion: Option[String] = None,
ignoreErrors: Boolean = false
): Either[BuildException, bo.BuildOptions] = either {
val releaseOpt = scalac.scalacOption.getScalacOption("-release")
val targetOpt = scalac.scalacOption.getScalacPrefixOption("-target")
jvm.jvm -> (releaseOpt.toSeq ++ targetOpt) match {
case (Some(j), compilerTargets) if compilerTargets.exists(_ != j) =>
val compilerTargetsString = compilerTargets.distinct.mkString(", ")
logger.error(
s"Warning: different target JVM ($j) and scala compiler target JVM ($compilerTargetsString) were passed."
)
case _ =>
}
val parsedPlatform = platform.map(Platform.normalize).flatMap(Platform.parse)
val platformOpt = value {
(parsedPlatform, js.js, native.native) match {
case (Some(p: Platform.JS.type), _, false) => Right(Some(p))
case (Some(p: Platform.Native.type), false, _) => Right(Some(p))
case (Some(p: Platform.JVM.type), false, false) => Right(Some(p))
case (Some(p), _, _) =>
val jsSeq = if (js.js) Seq(Platform.JS) else Seq.empty
val nativeSeq = if (native.native) Seq(Platform.Native) else Seq.empty
val platformsSeq = Seq(p) ++ jsSeq ++ nativeSeq
Left(new AmbiguousPlatformError(platformsSeq.distinct.map(_.toString)))
case (_, true, true) =>
Left(new AmbiguousPlatformError(Seq(Platform.JS.toString, Platform.Native.toString)))
case (_, true, _) => Right(Some(Platform.JS))
case (_, _, true) => Right(Some(Platform.Native))
case _ => Right(None)
}
}
val (assumedSourceJars, extraRegularJarsAndClasspath) =
extraJarsAndClassPath.partition(_.hasSourceJarSuffix)
if assumedSourceJars.nonEmpty then
val assumedSourceJarsString = assumedSourceJars.mkString(", ")
logger.message(
s"""[${Console.YELLOW}warn${Console.RESET}] Jars with the ${ScalaCliConsole.GRAY}*-sources.jar${Console.RESET} name suffix are assumed to be source jars.
|The following jars were assumed to be source jars and will be treated as such: $assumedSourceJarsString""".stripMargin
)
val resolvedToolkitDependency = SharedOptions.resolveToolkitDependency(withToolkit, logger)
bo.BuildOptions(
sourceGeneratorOptions = bo.SourceGeneratorOptions(
useBuildInfo = sourceGenerator.useBuildInfo,
projectVersion = sharedVersionOptions.projectVersion,
computeVersion = value {
sharedVersionOptions.computeVersion
.map(Positioned.commandLine)
.map(ComputeVersion.parse)
.sequence
}
),
suppressWarningOptions =
bo.SuppressWarningOptions(
suppressDirectivesInMultipleFilesWarning = getOptionOrFromConfig(
suppress.suppressDirectivesInMultipleFilesWarning,
Keys.suppressDirectivesInMultipleFilesWarning
),
suppressOutdatedDependencyWarning = getOptionOrFromConfig(
suppress.suppressOutdatedDependencyWarning,
Keys.suppressOutdatedDependenciessWarning
),
suppressExperimentalFeatureWarning = getOptionOrFromConfig(
suppress.global.suppressExperimentalFeatureWarning,
Keys.suppressExperimentalFeatureWarning
)
),
scalaOptions = bo.ScalaOptions(
scalaVersion = scalaVersion
.map(_.trim)
.filter(_.nonEmpty)
.map(bo.MaybeScalaVersion(_)),
scalaBinaryVersion = scalaBinaryVersion.map(_.trim).filter(_.nonEmpty),
addScalaLibrary = scalaLibrary.orElse(java.map(!_)),
addScalaCompiler = withCompiler,
generateSemanticDbs = semanticDb,
scalacOptions = scalac
.scalacOption
.withScalacExtraOptions(scalacExtra)
.toScalacOptShadowingSeq
.filterNonRedirected
.filterNonDeprecated
.map(Positioned.commandLine),
compilerPlugins =
SharedOptions.parseDependencies(
dependencies.compilerPlugin.map(Positioned.none),
ignoreErrors
),
platform = platformOpt.map(o => Positioned(List(Position.CommandLine()), o))
),
scriptOptions = bo.ScriptOptions(
forceObjectWrapper = objectWrapper
),
scalaJsOptions = scalaJsOptions(js),
scalaNativeOptions = scalaNativeOptions(native),
javaOptions = value(scala.cli.commands.util.JvmUtils.javaOptions(jvm)),
jmhOptions = bo.JmhOptions(
addJmhDependencies =
if (enableJmh) jmhVersion.orElse(Some(Constants.jmhVersion))
else None,
runJmh = if (enableJmh) Some(true) else None
),
classPathOptions = bo.ClassPathOptions(
extraClassPath = extraRegularJarsAndClasspath,
extraCompileOnlyJars = extraCompileOnlyClassPath,
extraSourceJars = extraSourceJars.extractedClassPath ++ assumedSourceJars,
extraRepositories = dependencies.repository.map(_.trim).filter(_.nonEmpty),
extraDependencies = ShadowingSeq.from(
SharedOptions.parseDependencies(
dependencies.dependency.map(Positioned.none),
ignoreErrors
) ++ resolvedToolkitDependency
),
extraCompileOnlyDependencies = ShadowingSeq.from(
SharedOptions.parseDependencies(
dependencies.compileOnlyDependency.map(Positioned.none),
ignoreErrors
) ++ resolvedToolkitDependency
)
),
internal = bo.InternalOptions(
cache = Some(coursierCache),
localRepository = LocalRepo.localRepo(Directories.directories.localRepoDir),
verbosity = Some(logging.verbosity),
strictBloopJsonCheck = strictBloopJsonCheck,
interactive = Some(() => interactive),
exclude = exclude.map(Positioned.commandLine),
offline = coursier.getOffline()
),
notForBloopOptions = bo.PostBuildOptions(
scalaJsLinkerOptions = linkerOptions(js),
addRunnerDependencyOpt = runner,
python = sharedPython.python,
pythonSetup = sharedPython.pythonSetup,
scalaPyVersion = sharedPython.scalaPyVersion
)
)
}
extension (rawClassPath: List[String]) {
def extractedClassPath: List[os.Path] =
rawClassPath
.flatMap(_.split(File.pathSeparator).toSeq)
.filter(_.nonEmpty)
.distinct
.map(os.Path(_, os.pwd))
.flatMap {
case cp if os.isDir(cp) =>
val jarsInTheDirectory =
os.walk(cp)
.filter(p => os.isFile(p) && p.last.endsWith(".jar"))
List(cp) ++ jarsInTheDirectory // .jar paths have to be passed directly, unlike .class
case cp => List(cp)
}
}
def extraJarsAndClassPath: List[os.Path] =
(extraJars ++ scalac.scalacOption.getScalacOption("-classpath"))
.extractedClassPath
def extraClasspathWasPassed: Boolean = extraJarsAndClassPath.exists(!_.hasSourceJarSuffix)
def extraCompileOnlyClassPath: List[os.Path] = extraCompileOnlyJars.extractedClassPath
def globalInteractiveWasSuggested: Either[BuildException, Option[Boolean]] = either {
value(ConfigDbUtils.configDb).get(Keys.globalInteractiveWasSuggested) match {
case Right(opt) => opt
case Left(ex) =>
logger.debug(ConfigDbException(ex))
None
}
}
def interactive: Either[BuildException, Interactive] = either {
(
logging.verbosityOptions.interactive,
value(ConfigDbUtils.configDb).get(Keys.interactive) match {
case Right(opt) => opt
case Left(ex) =>
logger.debug(ConfigDbException(ex))
None
},
value(globalInteractiveWasSuggested)
) match {
case (Some(true), _, Some(true)) => InteractiveAsk
case (_, Some(true), _) => InteractiveAsk
case (Some(true), _, _) =>
val answers @ List(yesAnswer, _) = List("Yes", "No")
InteractiveAsk.chooseOne(
s"""You have run the current ${ScalaCli.baseRunnerName} command with the --interactive mode turned on.
|Would you like to leave it on permanently?""".stripMargin,
answers
) match {
case Some(answer) if answer == yesAnswer =>
val configDb0 = value(ConfigDbUtils.configDb)
value {
configDb0
.set(Keys.interactive, true)
.set(Keys.globalInteractiveWasSuggested, true)
.save(Directories.directories.dbPath.toNIO)
.wrapConfigException
}
logger.message(
s"--interactive is now set permanently. All future ${ScalaCli.baseRunnerName} commands will run with the flag set to true."
)
logger.message(
s"If you want to turn this setting off at any point, just run `${ScalaCli.baseRunnerName} config interactive false`."
)
case _ =>
val configDb0 = value(ConfigDbUtils.configDb)
value {
configDb0
.set(Keys.globalInteractiveWasSuggested, true)
.save(Directories.directories.dbPath.toNIO)
.wrapConfigException
}
logger.message(
s"If you want to turn this setting permanently on at any point, just run `${ScalaCli.baseRunnerName} config interactive true`."
)
}
InteractiveAsk
case _ => InteractiveNop
}
}
def getOptionOrFromConfig(cliOption: Option[Boolean], configDbKey: BooleanEntry) =
cliOption.orElse(
ConfigDbUtils.configDb.map(_.get(configDbKey))
.map {
case Right(opt) => opt
case Left(ex) =>
logger.debug(ConfigDbException(ex))
None
}
.getOrElse(None)
)
def bloopRifleConfig(extraBuildOptions: Option[BuildOptions] = None)
: Either[BuildException, BloopRifleConfig] = either {
val options = extraBuildOptions.foldLeft(value(buildOptions(false, None)))(_ orElse _)
lazy val defaultJvmHome = value {
JvmUtils.downloadJvm(OsLibc.defaultJvm(OsLibc.jvmIndexOs), options)
}
val javaHomeInfo = compilationServer.bloopJvm
.map(jvmId => value(JvmUtils.downloadJvm(jvmId, options)))
.orElse {
for (javaHome <- options.javaHomeLocationOpt()) yield {
val (javaHomeVersion, javaHomeCmd) = OsLibc.javaHomeVersion(javaHome.value)
if (javaHomeVersion >= 17)
BuildOptions.JavaHomeInfo(javaHome.value, javaHomeCmd, javaHomeVersion)
else defaultJvmHome
}
}.getOrElse(defaultJvmHome)
compilationServer.bloopRifleConfig(
logging.logger,
coursierCache,
logging.verbosity,
javaHomeInfo.javaCommand,
Directories.directories,
Some(javaHomeInfo.version)
)
}
def compilerMaker(
threads: BuildThreads,
scaladoc: Boolean = false
): Either[BuildException, ScalaCompilerMaker] =
if (scaladoc)
Right(SimpleScalaCompilerMaker("java", Nil, scaladoc = true))
else if (compilationServer.server.getOrElse(true))
bloopRifleConfig() match {
case Right(config) =>
Right(new BloopCompilerMaker(
config,
threads.bloop,
strictBloopJsonCheckOrDefault,
coursier.getOffline().getOrElse(false)
))
case Left(ex) if coursier.getOffline().contains(true) =>
logger.diagnostic(WarningMessages.offlineModeBloopJvmNotFound, Severity.Warning)
Right(SimpleScalaCompilerMaker("java", Nil))
case Left(ex) => Left(ex)
}
else
Right(SimpleScalaCompilerMaker("java", Nil))
lazy val coursierCache = coursier.coursierCache(logging.logger.coursierLogger(""))
def inputs(
args: Seq[String],
defaultInputs: () => Option[Inputs] = () => Inputs.default()
)(using ScalaCliInvokeData): Either[BuildException, Inputs] =
SharedOptions.inputs(
args,
defaultInputs,
resourceDirs,
Directories.directories,
logger = logger,
coursierCache,
workspace.forcedWorkspaceOpt,
input.defaultForbiddenDirectories,
input.forbid,
scriptSnippetList = allScriptSnippets,
scalaSnippetList = allScalaSnippets,
javaSnippetList = allJavaSnippets,
markdownSnippetList = allMarkdownSnippets,
enableMarkdown = markdown.enableMarkdown,
extraClasspathWasPassed = extraClasspathWasPassed
)
def allScriptSnippets: List[String] = snippet.scriptSnippet ++ snippet.executeScript
def allScalaSnippets: List[String] = snippet.scalaSnippet ++ snippet.executeScala
def allJavaSnippets: List[String] = snippet.javaSnippet ++ snippet.executeJava
def allMarkdownSnippets: List[String] = snippet.markdownSnippet ++ snippet.executeMarkdown
def validateInputArgs(
args: Seq[String]
)(using ScalaCliInvokeData): Seq[Either[String, Seq[Element]]] =
Inputs.validateArgs(
args,
Os.pwd,
SharedOptions.downloadInputs(coursierCache),
SharedOptions.readStdin(logger = logger),
!Properties.isWin,
enableMarkdown = true
)
def strictBloopJsonCheckOrDefault: Boolean =
strictBloopJsonCheck.getOrElse(bo.InternalOptions.defaultStrictBloopJsonCheck)
}
object SharedOptions {
implicit lazy val parser: Parser[SharedOptions] = Parser.derive
implicit lazy val help: Help[SharedOptions] = Help.derive
implicit lazy val jsonCodec: JsonValueCodec[SharedOptions] = JsonCodecMaker.make
private def downloadInputs(cache: FileCache[Task]): String => Either[String, Array[Byte]] = {
url =>
val artifact = Artifact(url).withChanging(true)
cache.fileWithTtl0(artifact)
.left
.map(_.describe)
.map(f => os.read.bytes(os.Path(f, Os.pwd)))
}
/** [[Inputs]] builder, handy when you don't have a [[SharedOptions]] instance at hand */
def inputs(
args: Seq[String],
defaultInputs: () => Option[Inputs],
resourceDirs: Seq[String],
directories: scala.build.Directories,
logger: scala.build.Logger,
cache: FileCache[Task],
forcedWorkspaceOpt: Option[os.Path],
defaultForbiddenDirectories: Boolean,
forbid: List[String],
scriptSnippetList: List[String],
scalaSnippetList: List[String],
javaSnippetList: List[String],
markdownSnippetList: List[String],
enableMarkdown: Boolean = false,
extraClasspathWasPassed: Boolean = false
)(using ScalaCliInvokeData): Either[BuildException, Inputs] = {
val resourceInputs = resourceDirs
.map(os.Path(_, Os.pwd))
.map { path =>
if (!os.exists(path))
logger.message(s"WARNING: provided resource directory path doesn't exist: $path")
path
}
.map(ResourceDirectory.apply)
val maybeInputs = Inputs(
args,
Os.pwd,
defaultInputs = defaultInputs,
download = downloadInputs(cache),
stdinOpt = readStdin(logger = logger),
scriptSnippetList = scriptSnippetList,
scalaSnippetList = scalaSnippetList,
javaSnippetList = javaSnippetList,
markdownSnippetList = markdownSnippetList,
acceptFds = !Properties.isWin,
forcedWorkspace = forcedWorkspaceOpt,
enableMarkdown = enableMarkdown,
allowRestrictedFeatures = ScalaCli.allowRestrictedFeatures,
extraClasspathWasPassed = extraClasspathWasPassed
)
maybeInputs.map { inputs =>
val forbiddenDirs =
(if (defaultForbiddenDirectories) myDefaultForbiddenDirectories else Nil) ++
forbid.filter(_.trim.nonEmpty).map(os.Path(_, Os.pwd))
inputs
.add(resourceInputs)
.checkAttributes(directories)
.avoid(forbiddenDirs, directories)
}
}
private def readStdin(in: InputStream = System.in, logger: Logger): Option[Array[Byte]] =
if (in == null) {
logger.debug("No stdin available")
None
}
else {
logger.debug("Reading stdin")
val result = in.readAllBytes()
logger.debug(s"Done reading stdin (${result.length} B)")
Some(result)
}
private def myDefaultForbiddenDirectories: Seq[os.Path] =
if (Properties.isWin)
Seq(os.Path("""C:\Windows\System32"""))
else
Nil
def parseDependencies(
deps: List[Positioned[String]],
ignoreErrors: Boolean
): Seq[Positioned[AnyDependency]] =
deps.map(_.map(_.trim)).filter(_.value.nonEmpty)
.flatMap { posDepStr =>
val depStr = posDepStr.value
DependencyParser.parse(depStr) match {
case Left(err) =>
if (ignoreErrors) Nil
else sys.error(s"Error parsing dependency '$depStr': $err")
case Right(dep) => Seq(posDepStr.map(_ => dep))
}
}
// TODO: remove this state after resolving https://github.com/VirtusLab/scala-cli/issues/2658
private val loggedDeprecatedToolkitWarning: AtomicBoolean = AtomicBoolean(false)
private def resolveToolkitDependency(
toolkitVersion: Option[String],
logger: Logger
): Seq[Positioned[AnyDependency]] = {
if (
(toolkitVersion.contains("latest")
|| toolkitVersion.contains(Toolkit.typelevel + ":latest")
|| toolkitVersion.contains(
Constants.typelevelOrganization + ":latest"
)) && !loggedDeprecatedToolkitWarning.getAndSet(true)
) logger.message(
WarningMessages.deprecatedToolkitLatest(
s"--toolkit ${toolkitVersion.map(_.replace("latest", "default")).getOrElse("default")}"
)
)
toolkitVersion.toList.map(Positioned.commandLine)
.flatMap(Toolkit.resolveDependenciesWithRequirements(_).map(_.value))
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy