Skip to content

Handle help, version and @file parameters in scalac and scaladoc #11476

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/Driver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ class Driver {

def setup(args: Array[String], rootCtx: Context): (List[AbstractFile], Context) = {
val ictx = rootCtx.fresh
val summary = CompilerCommand.distill(args)(using ictx)
val summary = CompilerCommand.distill(args, config.ScalaSettings(), ictx.settingsState)
ictx.setSettings(summary.sstate)
MacroClassLoader.init(ictx)
Positioned.init(using ictx)

inContext(ictx) {
if !ctx.settings.YdropComments.value || ctx.mode.is(Mode.ReadComments) then
ictx.setProperty(ContextDoc, new ContextDocstrings)
val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)
val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(using ctx.settings)(using ctx.settingsState)
val files = fileNames.map(ctx.getFile)
(files, fromTastySetup(files))
}
Expand Down
141 changes: 141 additions & 0 deletions compiler/src/dotty/tools/dotc/config/CliCommand.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package dotty.tools.dotc
package config

import java.nio.file.{Files, Paths}

import Settings._
import core.Contexts._
import Properties._

import scala.collection.JavaConverters._

trait CliCommand:

type ConcreteSettings <: CommonScalaSettings with Settings.SettingGroup

/** The name of the command */
def cmdName: String

private def explainAdvanced = """
|-- Notes on option parsing --
|Boolean settings are always false unless set.
|Where multiple values are accepted, they should be comma-separated.
| example: -Xplugin:plugin1,plugin2
|<phases> means one or a comma-separated list of:
| - (partial) phase names with an optional "+" suffix to include the next phase
| - the string "all"
| example: -Xprint:all prints all phases.
| example: -Xprint:typer,mixin prints the typer and mixin phases.
| example: -Ylog:erasure+ logs the erasure phase and the phase after the erasure phase.
| This is useful because during the tree transform of phase X, we often
| already are in phase X + 1.
"""

def shortUsage: String = s"Usage: $cmdName <options> <source files>"

def versionMsg: String = s"Scala $versionString -- $copyrightString"

def ifErrorsMsg: String = " -help gives more information"

def shouldStopWithInfo(using settings: ConcreteSettings)(using SettingsState): Boolean

/** Distill arguments into summary detailing settings, errors and files to main */
def distill(args: Array[String], sg: Settings.SettingGroup, ss: SettingsState): ArgsSummary =
/**
* Expands all arguments starting with @ to the contents of the
* file named like each argument.
*/
def expandArg(arg: String): List[String] =
def stripComment(s: String) = s takeWhile (_ != '#')
val path = Paths.get(arg stripPrefix "@")
if (!Files.exists(path))
throw new java.io.FileNotFoundException("argument file %s could not be found" format path.getFileName)

val lines = Files.readAllLines(path) // default to UTF-8 encoding

val params = lines.asScala map stripComment mkString " "
CommandLineParser.tokenize(params)

// expand out @filename to the contents of that filename
def expandedArguments = args.toList flatMap {
case x if x startsWith "@" => expandArg(x)
case x => List(x)
}

sg.processArguments(expandedArguments, ss, processAll = true)


def infoMessage(using settings: ConcreteSettings)(using SettingsState)(using Context): String

/** Creates a help message for a subset of options based on cond */
def availableOptionsMsg(cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
val ss = (settings.allSettings filter cond).toList sortBy (_.name)
val width = (ss map (_.name.length)).max
def format(s: String) = ("%-" + width + "s") format s
def helpStr(s: Setting[?]) =
def defaultValue = s.default match
case _: Int | _: String => s.default.toString
case _ =>
// For now, skip the default values that do not make sense for the end user.
// For example 'false' for the version command.
""

def formatSetting(name: String, value: String) =
if (value.nonEmpty)
// the format here is helping to make empty padding and put the additional information exactly under the description.
s"\n${format("")} $name: $value."
else
""
s"${format(s.name)} ${s.description}${formatSetting("Default", defaultValue)}${formatSetting("Choices", s.legalChoices)}"

ss.map(helpStr).mkString("", "\n", s"\n${format("@<file>")} A text file containing compiler arguments (options and source files).\n")


def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
val prefix = List(
Some(shortUsage),
Some(explainAdvanced) filter (_ => shouldExplain),
Some(label + " options include:")
).flatten mkString "\n"

prefix + "\n" + availableOptionsMsg(cond)

def isStandard(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = !isAdvanced(s) && !isPrivate(s)
def isAdvanced(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = s.name.startsWith("-X") && s.name != "-X"
def isPrivate(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = s.name.startsWith("-Y") && s.name != "-Y"

/** Messages explaining usage and options */
def usageMessage(using settings: ConcreteSettings)(using SettingsState) = createUsageMsg("where possible standard", shouldExplain = false, isStandard)
def xusageMessage(using settings: ConcreteSettings)(using SettingsState) = createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced)
def yusageMessage(using settings: ConcreteSettings)(using SettingsState) = createUsageMsg("Possible private", shouldExplain = true, isPrivate)

def phasesMessage: String =
(new Compiler()).phases.map {
case List(single) => single.phaseName
case more => more.map(_.phaseName).mkString("{", ", ", "}")
}.mkString("\n")

/** Provide usage feedback on argument summary, assuming that all settings
* are already applied in context.
* @return The list of files passed as arguments.
*/
def checkUsage(summary: ArgsSummary, sourcesRequired: Boolean)(using settings: ConcreteSettings)(using SettingsState)(using Context): List[String] =
// Print all warnings encountered during arguments parsing
summary.warnings.foreach(report.warning(_))

if summary.errors.nonEmpty then
summary.errors foreach (report.error(_))
report.echo(ifErrorsMsg)
Nil
else if settings.version.value then
report.echo(versionMsg)
Nil
else if shouldStopWithInfo then
report.echo(infoMessage)
Nil
else
if (sourcesRequired && summary.arguments.isEmpty) report.echo(usageMessage)
summary.arguments

extension [T](setting: Setting[T])
protected def value(using ss: SettingsState): T = setting.valueIn(ss)
162 changes: 16 additions & 146 deletions compiler/src/dotty/tools/dotc/config/CompilerCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,149 +9,19 @@ import Properties._

import scala.collection.JavaConverters._

object CompilerCommand {

/** The name of the command */
def cmdName: String = "scalac"

private def explainAdvanced = """
|-- Notes on option parsing --
|Boolean settings are always false unless set.
|Where multiple values are accepted, they should be comma-separated.
| example: -Xplugin:plugin1,plugin2
|<phases> means one or a comma-separated list of:
| - (partial) phase names with an optional "+" suffix to include the next phase
| - the string "all"
| example: -Xprint:all prints all phases.
| example: -Xprint:typer,mixin prints the typer and mixin phases.
| example: -Ylog:erasure+ logs the erasure phase and the phase after the erasure phase.
| This is useful because during the tree transform of phase X, we often
| already are in phase X + 1.
"""

def shortUsage: String = s"Usage: $cmdName <options> <source files>"

def versionMsg: String = s"Scala compiler $versionString -- $copyrightString"

def shouldStopWithInfo(using Context): Boolean = {
val settings = ctx.settings
import settings._
Set(help, Xhelp, Yhelp, showPlugins, XshowPhases) exists (_.value)
}

/** Distill arguments into summary detailing settings, errors and files to compiler */
def distill(args: Array[String])(using Context): ArgsSummary = {
/**
* Expands all arguments starting with @ to the contents of the
* file named like each argument.
*/
def expandArg(arg: String): List[String] = {
def stripComment(s: String) = s takeWhile (_ != '#')
val path = Paths.get(arg stripPrefix "@")
if (!Files.exists(path))
throw new java.io.FileNotFoundException("argument file %s could not be found" format path.getFileName)

val lines = Files.readAllLines(path) // default to UTF-8 encoding

val params = lines.asScala map stripComment mkString " "
CommandLineParser.tokenize(params)
}

// expand out @filename to the contents of that filename
def expandedArguments = args.toList flatMap {
case x if x startsWith "@" => expandArg(x)
case x => List(x)
}

ctx.settings.processArguments(expandedArguments, processAll = true)
}

/** Provide usage feedback on argument summary, assuming that all settings
* are already applied in context.
* @return The list of files to compile.
*/
def checkUsage(summary: ArgsSummary, sourcesRequired: Boolean)(using Context): List[String] = {
val settings = ctx.settings

/** Creates a help message for a subset of options based on cond */
def availableOptionsMsg(cond: Setting[?] => Boolean): String = {
val ss = (ctx.settings.allSettings filter cond).toList sortBy (_.name)
val width = (ss map (_.name.length)).max
def format(s: String) = ("%-" + width + "s") format s
def helpStr(s: Setting[?]) = {
def defaultValue = s.default match {
case _: Int | _: String => s.default.toString
case _ =>
// For now, skip the default values that do not make sense for the end user.
// For example 'false' for the version command.
""
}
def formatSetting(name: String, value: String) =
if (value.nonEmpty)
// the format here is helping to make empty padding and put the additional information exactly under the description.
s"\n${format("")} $name: $value."
else
""
s"${format(s.name)} ${s.description}${formatSetting("Default", defaultValue)}${formatSetting("Choices", s.legalChoices)}"
}
ss map helpStr mkString "\n"
}

def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean): String = {
val prefix = List(
Some(shortUsage),
Some(explainAdvanced) filter (_ => shouldExplain),
Some(label + " options include:")
).flatten mkString "\n"

prefix + "\n" + availableOptionsMsg(cond)
}

def isStandard(s: Setting[?]): Boolean = !isAdvanced(s) && !isPrivate(s)
def isAdvanced(s: Setting[?]): Boolean = s.name.startsWith("-X") && s.name != "-X"
def isPrivate(s: Setting[?]) : Boolean = s.name.startsWith("-Y") && s.name != "-Y"

/** Messages explaining usage and options */
def usageMessage = createUsageMsg("where possible standard", shouldExplain = false, isStandard)
def xusageMessage = createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced)
def yusageMessage = createUsageMsg("Possible private", shouldExplain = true, isPrivate)

def phasesMessage: String = {
(new Compiler()).phases.map {
case List(single) => single.phaseName
case more => more.map(_.phaseName).mkString("{", ", ", "}")
}.mkString("\n")
}

def infoMessage: String = {
import settings._
if (help.value) usageMessage
else if (Xhelp.value) xusageMessage
else if (Yhelp.value) yusageMessage
else if (showPlugins.value) ctx.base.pluginDescriptions
else if (XshowPhases.value) phasesMessage
else ""
}

// Print all warnings encountered during arguments parsing
summary.warnings.foreach(report.warning(_))

if (summary.errors.nonEmpty) {
summary.errors foreach (report.error(_))
report.echo(" scalac -help gives more information")
Nil
}
else if (settings.version.value) {
report.echo(versionMsg)
Nil
}
else if (shouldStopWithInfo) {
report.echo(infoMessage)
Nil
}
else {
if (sourcesRequired && summary.arguments.isEmpty) report.echo(usageMessage)
summary.arguments
}
}
}
object CompilerCommand extends CliCommand:
type ConcreteSettings = ScalaSettings
override def cmdName: String = "scalac"
override def versionMsg: String = s"Scala compiler $versionString -- $copyrightString"
override def ifErrorsMsg: String = " scalac -help gives more information"

def infoMessage(using settings: ScalaSettings)(using SettingsState)(using Context): String =
if (settings.help.value) usageMessage
else if (settings.Xhelp.value) xusageMessage
else if (settings.Yhelp.value) yusageMessage
else if (settings.showPlugins.value) ctx.base.pluginDescriptions
else if (settings.XshowPhases.value) phasesMessage
else ""

def shouldStopWithInfo(using settings: ScalaSettings)(using SettingsState): Boolean =
Set(settings.help, settings.Xhelp, settings.Yhelp, settings.showPlugins, settings.XshowPhases) exists (_.value)
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/PathResolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ object PathResolver {
}
else inContext(ContextBase().initialCtx) {
val ArgsSummary(sstate, rest, errors, warnings) =
ctx.settings.processArguments(args.toList, true)
ctx.settings.processArguments(args.toList, ctx.settingsState, true)
errors.foreach(println)
val pr = new PathResolver()(using ctx.fresh.setSettings(sstate))
println(" COMMAND: 'scala %s'".format(args.mkString(" ")))
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ trait CommonScalaSettings { self: Settings.SettingGroup =>
val color: Setting[String] = ChoiceSetting("-color", "mode", "Colored output", List("always", "never"/*, "auto"*/), "always"/* "auto"*/, aliases = List("--color"))
val verbose: Setting[Boolean] = BooleanSetting("-verbose", "Output messages about what the compiler is doing.", aliases = List("--verbose"))
val version: Setting[Boolean] = BooleanSetting("-version", "Print product version and exit.", aliases = List("--version"))
val help: Setting[Boolean] = BooleanSetting("-help", "Print a synopsis of standard options.", aliases = List("--help"))
val pageWidth: Setting[Int] = IntSetting("-pagewidth", "Set page width", 80, aliases = List("--page-width"))
val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.", aliases = List("--no-warnings"))

Expand Down Expand Up @@ -93,7 +94,6 @@ class ScalaSettings extends Settings.SettingGroup with CommonScalaSettings {
val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail.", aliases = List("--explain-types"))
val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain"))
val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature"))
val help: Setting[Boolean] = BooleanSetting("-help", "Print a synopsis of standard options.", aliases = List("--help"))
val release: Setting[String] = ChoiceSetting("-release", "release", "Compile code with classes specific to the given version of the Java platform available on the classpath and emit bytecode for this version.", supportedReleaseVersions, "", aliases = List("--release"))
val source: Setting[String] = ChoiceSetting("-source", "source version", "source version", List("3.0", "future", "3.0-migration", "future-migration"), "3.0", aliases = List("--source"))
val scalajs: Setting[Boolean] = BooleanSetting("-scalajs", "Compile in Scala.js mode (requires scalajs-library.jar on the classpath).", aliases = List("--scalajs"))
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/config/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ object Settings {
}
}

def processArguments(arguments: List[String], processAll: Boolean)(using Context): ArgsSummary =
processArguments(ArgsSummary(ctx.settingsState, arguments, Nil, Nil), processAll, Nil)
def processArguments(arguments: List[String], settingsState: SettingsState, processAll: Boolean): ArgsSummary =
processArguments(ArgsSummary(settingsState, arguments, Nil, Nil), processAll, Nil)

def publish[T](settingf: Int => Setting[T]): Setting[T] = {
val setting = settingf(_allSettings.length)
Expand Down
Loading