Skip to content

Commit ce13b5e

Browse files
committed
Handle help, version and @file parameters in scalac and scaladoc
1 parent 9e34c72 commit ce13b5e

File tree

11 files changed

+360
-318
lines changed

11 files changed

+360
-318
lines changed

compiler/src/dotty/tools/dotc/Driver.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ class Driver {
6666

6767
def setup(args: Array[String], rootCtx: Context): (List[AbstractFile], Context) = {
6868
val ictx = rootCtx.fresh
69-
val summary = CompilerCommand.distill(args)(using ictx)
69+
val summary = CompilerCommand.distill(args, config.ScalaSettings(), ictx.settingsState)
7070
ictx.setSettings(summary.sstate)
7171
MacroClassLoader.init(ictx)
7272
Positioned.init(using ictx)
7373

7474
inContext(ictx) {
7575
if !ctx.settings.YdropComments.value || ctx.mode.is(Mode.ReadComments) then
7676
ictx.setProperty(ContextDoc, new ContextDocstrings)
77-
val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)
77+
val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(using ctx.settings)(using ctx.settingsState)
7878
val files = fileNames.map(ctx.getFile)
7979
(files, fromTastySetup(files))
8080
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package dotty.tools.dotc
2+
package config
3+
4+
import java.nio.file.{Files, Paths}
5+
6+
import Settings._
7+
import core.Contexts._
8+
import Properties._
9+
10+
import scala.collection.JavaConverters._
11+
12+
trait CliCommand:
13+
14+
type ConcreteSettings <: CommonScalaSettings with Settings.SettingGroup
15+
16+
/** The name of the command */
17+
def cmdName: String
18+
19+
private def explainAdvanced = """
20+
|-- Notes on option parsing --
21+
|Boolean settings are always false unless set.
22+
|Where multiple values are accepted, they should be comma-separated.
23+
| example: -Xplugin:plugin1,plugin2
24+
|<phases> means one or a comma-separated list of:
25+
| - (partial) phase names with an optional "+" suffix to include the next phase
26+
| - the string "all"
27+
| example: -Xprint:all prints all phases.
28+
| example: -Xprint:typer,mixin prints the typer and mixin phases.
29+
| example: -Ylog:erasure+ logs the erasure phase and the phase after the erasure phase.
30+
| This is useful because during the tree transform of phase X, we often
31+
| already are in phase X + 1.
32+
"""
33+
34+
def shortUsage: String = s"Usage: $cmdName <options> <source files>"
35+
36+
def versionMsg: String = s"Scala $versionString -- $copyrightString"
37+
38+
def ifErrorsMsg: String = " -help gives more information"
39+
40+
def shouldStopWithInfo(using settings: ConcreteSettings)(using SettingsState): Boolean
41+
42+
/** Distill arguments into summary detailing settings, errors and files to main */
43+
def distill(args: Array[String], sg: Settings.SettingGroup, ss: SettingsState): ArgsSummary =
44+
/**
45+
* Expands all arguments starting with @ to the contents of the
46+
* file named like each argument.
47+
*/
48+
def expandArg(arg: String): List[String] =
49+
def stripComment(s: String) = s takeWhile (_ != '#')
50+
val path = Paths.get(arg stripPrefix "@")
51+
if (!Files.exists(path))
52+
throw new java.io.FileNotFoundException("argument file %s could not be found" format path.getFileName)
53+
54+
val lines = Files.readAllLines(path) // default to UTF-8 encoding
55+
56+
val params = lines.asScala map stripComment mkString " "
57+
CommandLineParser.tokenize(params)
58+
59+
// expand out @filename to the contents of that filename
60+
def expandedArguments = args.toList flatMap {
61+
case x if x startsWith "@" => expandArg(x)
62+
case x => List(x)
63+
}
64+
65+
sg.processArguments(expandedArguments, ss, processAll = true)
66+
67+
68+
def infoMessage(using settings: ConcreteSettings)(using SettingsState)(using Context): String
69+
70+
/** Creates a help message for a subset of options based on cond */
71+
def availableOptionsMsg(cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
72+
val ss = (settings.allSettings filter cond).toList sortBy (_.name)
73+
val width = (ss map (_.name.length)).max
74+
def format(s: String) = ("%-" + width + "s") format s
75+
def helpStr(s: Setting[?]) =
76+
def defaultValue = s.default match
77+
case _: Int | _: String => s.default.toString
78+
case _ =>
79+
// For now, skip the default values that do not make sense for the end user.
80+
// For example 'false' for the version command.
81+
""
82+
83+
def formatSetting(name: String, value: String) =
84+
if (value.nonEmpty)
85+
// the format here is helping to make empty padding and put the additional information exactly under the description.
86+
s"\n${format("")} $name: $value."
87+
else
88+
""
89+
s"${format(s.name)} ${s.description}${formatSetting("Default", defaultValue)}${formatSetting("Choices", s.legalChoices)}"
90+
91+
ss.map(helpStr).mkString("", "\n", s"\n${format("@<file>")} A text file containing compiler arguments (options and source files).\n")
92+
93+
94+
def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
95+
val prefix = List(
96+
Some(shortUsage),
97+
Some(explainAdvanced) filter (_ => shouldExplain),
98+
Some(label + " options include:")
99+
).flatten mkString "\n"
100+
101+
prefix + "\n" + availableOptionsMsg(cond)
102+
103+
def isStandard(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = !isAdvanced(s) && !isPrivate(s)
104+
def isAdvanced(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = s.name.startsWith("-X") && s.name != "-X"
105+
def isPrivate(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = s.name.startsWith("-Y") && s.name != "-Y"
106+
107+
/** Messages explaining usage and options */
108+
def usageMessage(using settings: ConcreteSettings)(using SettingsState) = createUsageMsg("where possible standard", shouldExplain = false, isStandard)
109+
def xusageMessage(using settings: ConcreteSettings)(using SettingsState) = createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced)
110+
def yusageMessage(using settings: ConcreteSettings)(using SettingsState) = createUsageMsg("Possible private", shouldExplain = true, isPrivate)
111+
112+
def phasesMessage: String =
113+
(new Compiler()).phases.map {
114+
case List(single) => single.phaseName
115+
case more => more.map(_.phaseName).mkString("{", ", ", "}")
116+
}.mkString("\n")
117+
118+
/** Provide usage feedback on argument summary, assuming that all settings
119+
* are already applied in context.
120+
* @return The list of files passed as arguments.
121+
*/
122+
def checkUsage(summary: ArgsSummary, sourcesRequired: Boolean)(using settings: ConcreteSettings)(using SettingsState)(using Context): List[String] =
123+
// Print all warnings encountered during arguments parsing
124+
summary.warnings.foreach(report.warning(_))
125+
126+
if summary.errors.nonEmpty then
127+
summary.errors foreach (report.error(_))
128+
report.echo(ifErrorsMsg)
129+
Nil
130+
else if settings.version.value then
131+
report.echo(versionMsg)
132+
Nil
133+
else if shouldStopWithInfo then
134+
report.echo(infoMessage)
135+
Nil
136+
else
137+
if (sourcesRequired && summary.arguments.isEmpty) report.echo(usageMessage)
138+
summary.arguments
139+
140+
extension [T](setting: Setting[T])
141+
protected def value(using ss: SettingsState): T = setting.valueIn(ss)

compiler/src/dotty/tools/dotc/config/CompilerCommand.scala

+16-146
Original file line numberDiff line numberDiff line change
@@ -9,149 +9,19 @@ import Properties._
99

1010
import scala.collection.JavaConverters._
1111

12-
object CompilerCommand {
13-
14-
/** The name of the command */
15-
def cmdName: String = "scalac"
16-
17-
private def explainAdvanced = """
18-
|-- Notes on option parsing --
19-
|Boolean settings are always false unless set.
20-
|Where multiple values are accepted, they should be comma-separated.
21-
| example: -Xplugin:plugin1,plugin2
22-
|<phases> means one or a comma-separated list of:
23-
| - (partial) phase names with an optional "+" suffix to include the next phase
24-
| - the string "all"
25-
| example: -Xprint:all prints all phases.
26-
| example: -Xprint:typer,mixin prints the typer and mixin phases.
27-
| example: -Ylog:erasure+ logs the erasure phase and the phase after the erasure phase.
28-
| This is useful because during the tree transform of phase X, we often
29-
| already are in phase X + 1.
30-
"""
31-
32-
def shortUsage: String = s"Usage: $cmdName <options> <source files>"
33-
34-
def versionMsg: String = s"Scala compiler $versionString -- $copyrightString"
35-
36-
def shouldStopWithInfo(using Context): Boolean = {
37-
val settings = ctx.settings
38-
import settings._
39-
Set(help, Xhelp, Yhelp, showPlugins, XshowPhases) exists (_.value)
40-
}
41-
42-
/** Distill arguments into summary detailing settings, errors and files to compiler */
43-
def distill(args: Array[String])(using Context): ArgsSummary = {
44-
/**
45-
* Expands all arguments starting with @ to the contents of the
46-
* file named like each argument.
47-
*/
48-
def expandArg(arg: String): List[String] = {
49-
def stripComment(s: String) = s takeWhile (_ != '#')
50-
val path = Paths.get(arg stripPrefix "@")
51-
if (!Files.exists(path))
52-
throw new java.io.FileNotFoundException("argument file %s could not be found" format path.getFileName)
53-
54-
val lines = Files.readAllLines(path) // default to UTF-8 encoding
55-
56-
val params = lines.asScala map stripComment mkString " "
57-
CommandLineParser.tokenize(params)
58-
}
59-
60-
// expand out @filename to the contents of that filename
61-
def expandedArguments = args.toList flatMap {
62-
case x if x startsWith "@" => expandArg(x)
63-
case x => List(x)
64-
}
65-
66-
ctx.settings.processArguments(expandedArguments, processAll = true)
67-
}
68-
69-
/** Provide usage feedback on argument summary, assuming that all settings
70-
* are already applied in context.
71-
* @return The list of files to compile.
72-
*/
73-
def checkUsage(summary: ArgsSummary, sourcesRequired: Boolean)(using Context): List[String] = {
74-
val settings = ctx.settings
75-
76-
/** Creates a help message for a subset of options based on cond */
77-
def availableOptionsMsg(cond: Setting[?] => Boolean): String = {
78-
val ss = (ctx.settings.allSettings filter cond).toList sortBy (_.name)
79-
val width = (ss map (_.name.length)).max
80-
def format(s: String) = ("%-" + width + "s") format s
81-
def helpStr(s: Setting[?]) = {
82-
def defaultValue = s.default match {
83-
case _: Int | _: String => s.default.toString
84-
case _ =>
85-
// For now, skip the default values that do not make sense for the end user.
86-
// For example 'false' for the version command.
87-
""
88-
}
89-
def formatSetting(name: String, value: String) =
90-
if (value.nonEmpty)
91-
// the format here is helping to make empty padding and put the additional information exactly under the description.
92-
s"\n${format("")} $name: $value."
93-
else
94-
""
95-
s"${format(s.name)} ${s.description}${formatSetting("Default", defaultValue)}${formatSetting("Choices", s.legalChoices)}"
96-
}
97-
ss map helpStr mkString "\n"
98-
}
99-
100-
def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean): String = {
101-
val prefix = List(
102-
Some(shortUsage),
103-
Some(explainAdvanced) filter (_ => shouldExplain),
104-
Some(label + " options include:")
105-
).flatten mkString "\n"
106-
107-
prefix + "\n" + availableOptionsMsg(cond)
108-
}
109-
110-
def isStandard(s: Setting[?]): Boolean = !isAdvanced(s) && !isPrivate(s)
111-
def isAdvanced(s: Setting[?]): Boolean = s.name.startsWith("-X") && s.name != "-X"
112-
def isPrivate(s: Setting[?]) : Boolean = s.name.startsWith("-Y") && s.name != "-Y"
113-
114-
/** Messages explaining usage and options */
115-
def usageMessage = createUsageMsg("where possible standard", shouldExplain = false, isStandard)
116-
def xusageMessage = createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced)
117-
def yusageMessage = createUsageMsg("Possible private", shouldExplain = true, isPrivate)
118-
119-
def phasesMessage: String = {
120-
(new Compiler()).phases.map {
121-
case List(single) => single.phaseName
122-
case more => more.map(_.phaseName).mkString("{", ", ", "}")
123-
}.mkString("\n")
124-
}
125-
126-
def infoMessage: String = {
127-
import settings._
128-
if (help.value) usageMessage
129-
else if (Xhelp.value) xusageMessage
130-
else if (Yhelp.value) yusageMessage
131-
else if (showPlugins.value) ctx.base.pluginDescriptions
132-
else if (XshowPhases.value) phasesMessage
133-
else ""
134-
}
135-
136-
// Print all warnings encountered during arguments parsing
137-
summary.warnings.foreach(report.warning(_))
138-
139-
if (summary.errors.nonEmpty) {
140-
summary.errors foreach (report.error(_))
141-
report.echo(" scalac -help gives more information")
142-
Nil
143-
}
144-
else if (settings.version.value) {
145-
report.echo(versionMsg)
146-
Nil
147-
}
148-
else if (shouldStopWithInfo) {
149-
report.echo(infoMessage)
150-
Nil
151-
}
152-
else {
153-
if (sourcesRequired && summary.arguments.isEmpty) report.echo(usageMessage)
154-
summary.arguments
155-
}
156-
}
157-
}
12+
object CompilerCommand extends CliCommand:
13+
type ConcreteSettings = ScalaSettings
14+
override def cmdName: String = "scalac"
15+
override def versionMsg: String = s"Scala compiler $versionString -- $copyrightString"
16+
override def ifErrorsMsg: String = " scalac -help gives more information"
17+
18+
def infoMessage(using settings: ScalaSettings)(using SettingsState)(using Context): String =
19+
if (settings.help.value) usageMessage
20+
else if (settings.Xhelp.value) xusageMessage
21+
else if (settings.Yhelp.value) yusageMessage
22+
else if (settings.showPlugins.value) ctx.base.pluginDescriptions
23+
else if (settings.XshowPhases.value) phasesMessage
24+
else ""
25+
26+
def shouldStopWithInfo(using settings: ScalaSettings)(using SettingsState): Boolean =
27+
Set(settings.help, settings.Xhelp, settings.Yhelp, settings.showPlugins, settings.XshowPhases) exists (_.value)

compiler/src/dotty/tools/dotc/config/PathResolver.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ object PathResolver {
144144
}
145145
else inContext(ContextBase().initialCtx) {
146146
val ArgsSummary(sstate, rest, errors, warnings) =
147-
ctx.settings.processArguments(args.toList, true)
147+
ctx.settings.processArguments(args.toList, ctx.settingsState, true)
148148
errors.foreach(println)
149149
val pr = new PathResolver()(using ctx.fresh.setSettings(sstate))
150150
println(" COMMAND: 'scala %s'".format(args.mkString(" ")))

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ trait CommonScalaSettings { self: Settings.SettingGroup =>
2525
val color: Setting[String] = ChoiceSetting("-color", "mode", "Colored output", List("always", "never"/*, "auto"*/), "always"/* "auto"*/, aliases = List("--color"))
2626
val verbose: Setting[Boolean] = BooleanSetting("-verbose", "Output messages about what the compiler is doing.", aliases = List("--verbose"))
2727
val version: Setting[Boolean] = BooleanSetting("-version", "Print product version and exit.", aliases = List("--version"))
28+
val help: Setting[Boolean] = BooleanSetting("-help", "Print a synopsis of standard options.", aliases = List("--help"))
2829
val pageWidth: Setting[Int] = IntSetting("-pagewidth", "Set page width", 80, aliases = List("--page-width"))
2930
val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.", aliases = List("--no-warnings"))
3031

@@ -93,7 +94,6 @@ class ScalaSettings extends Settings.SettingGroup with CommonScalaSettings {
9394
val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail.", aliases = List("--explain-types"))
9495
val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain"))
9596
val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature"))
96-
val help: Setting[Boolean] = BooleanSetting("-help", "Print a synopsis of standard options.", aliases = List("--help"))
9797
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"))
9898
val source: Setting[String] = ChoiceSetting("-source", "source version", "source version", List("3.0", "future", "3.0-migration", "future-migration"), "3.0", aliases = List("--source"))
9999
val scalajs: Setting[Boolean] = BooleanSetting("-scalajs", "Compile in Scala.js mode (requires scalajs-library.jar on the classpath).", aliases = List("--scalajs"))

compiler/src/dotty/tools/dotc/config/Settings.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ object Settings {
244244
}
245245
}
246246

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

250250
def publish[T](settingf: Int => Setting[T]): Setting[T] = {
251251
val setting = settingf(_allSettings.length)

sbt-bridge/src/dotty/tools/xsbt/CompilerBridgeDriver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ synchronized public void run(VirtualFile[] sources, AnalysisCallback callback, L
6262

6363
Contexts.Context context = setup(args, initialCtx)._2;
6464

65-
if (CompilerCommand.shouldStopWithInfo(context)) {
65+
if (CompilerCommand.shouldStopWithInfo(context.settings(), context.settingsState())) {
6666
throw new InterfaceCompileFailed(args, new Problem[0], StopInfoError);
6767
}
6868

0 commit comments

Comments
 (0)