Skip to content

Commit 58c77c3

Browse files
authored
Merge pull request #11476 from BarkingBad/scala3doc/cli-fixes
Handle help, version and @file parameters in scalac and scaladoc
2 parents a4f3436 + c4c63fc commit 58c77c3

29 files changed

+540
-383
lines changed

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

+21-7
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,30 @@ class Driver {
6464

6565
protected def sourcesRequired: Boolean = true
6666

67-
def setup(args: Array[String], rootCtx: Context): (List[AbstractFile], Context) = {
67+
protected def command: CompilerCommand = ScalacCommand
68+
69+
/** Setup context with initialized settings from CLI arguments, then check if there are any settings that
70+
* would change the default behaviour of the compiler.
71+
*
72+
* @return If there is no setting like `-help` preventing us from continuing compilation,
73+
* this method returns a list of files to compile and an updated Context.
74+
* If compilation should be interrupted, this method returns None.
75+
*/
76+
def setup(args: Array[String], rootCtx: Context): Option[(List[AbstractFile], Context)] = {
6877
val ictx = rootCtx.fresh
69-
val summary = CompilerCommand.distill(args)(using ictx)
78+
val summary = command.distill(args, ictx.settings)(ictx.settingsState)(using ictx)
7079
ictx.setSettings(summary.sstate)
7180
MacroClassLoader.init(ictx)
7281
Positioned.init(using ictx)
7382

7483
inContext(ictx) {
7584
if !ctx.settings.YdropComments.value || ctx.mode.is(Mode.ReadComments) then
7685
ictx.setProperty(ContextDoc, new ContextDocstrings)
77-
val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)
78-
val files = fileNames.map(ctx.getFile)
79-
(files, fromTastySetup(files))
86+
val fileNamesOrNone = command.checkUsage(summary, sourcesRequired)(using ctx.settings)(using ctx.settingsState)
87+
fileNamesOrNone.map { fileNames =>
88+
val files = fileNames.map(ctx.getFile)
89+
(files, fromTastySetup(files))
90+
}
8091
}
8192
}
8293

@@ -182,8 +193,11 @@ class Driver {
182193
* if compilation succeeded.
183194
*/
184195
def process(args: Array[String], rootCtx: Context): Reporter = {
185-
val (files, compileCtx) = setup(args, rootCtx)
186-
doCompile(newCompiler(using compileCtx), files)(using compileCtx)
196+
setup(args, rootCtx) match
197+
case Some((files, compileCtx)) =>
198+
doCompile(newCompiler(using compileCtx), files)(using compileCtx)
199+
case None =>
200+
rootCtx.reporter
187201
}
188202

189203
def main(args: Array[String]): Unit = {

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

+15-10
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,21 @@ class Resident extends Driver {
4040

4141
final override def process(args: Array[String], rootCtx: Context): Reporter = {
4242
@tailrec def loop(args: Array[String], prevCtx: Context): Reporter = {
43-
var (files, ctx) = setup(args, prevCtx)
44-
inContext(ctx) { doCompile(residentCompiler, files) }
45-
var nextCtx = ctx
46-
var line = getLine()
47-
while (line == reset) {
48-
nextCtx = rootCtx
49-
line = getLine()
50-
}
51-
if (line.startsWith(quit)) ctx.reporter
52-
else loop(line split "\\s+", nextCtx)
43+
setup(args, prevCtx) match
44+
case Some((files, ctx)) =>
45+
inContext(ctx) {
46+
doCompile(residentCompiler, files)
47+
}
48+
var nextCtx = ctx
49+
var line = getLine()
50+
while (line == reset) {
51+
nextCtx = rootCtx
52+
line = getLine()
53+
}
54+
if (line.startsWith(quit)) ctx.reporter
55+
else loop(line split "\\s+", nextCtx)
56+
case None =>
57+
prevCtx.reporter
5358
}
5459
loop(args, rootCtx)
5560
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dotty.tools.dotc
2+
3+
import config.Properties._
4+
import config.CompilerCommand
5+
6+
object ScalacCommand extends CompilerCommand:
7+
override def cmdName: String = "scalac"
8+
override def versionMsg: String = s"Scala compiler $versionString -- $copyrightString"
9+
override def ifErrorsMsg: String = " scalac -help gives more information"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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+
def versionMsg: String
17+
18+
def ifErrorsMsg: String
19+
20+
/** The name of the command */
21+
def cmdName: String
22+
23+
def isHelpFlag(using settings: ConcreteSettings)(using SettingsState): Boolean
24+
25+
def helpMsg(using settings: ConcreteSettings)(using SettingsState, Context): String
26+
27+
private def explainAdvanced = """
28+
|-- Notes on option parsing --
29+
|Boolean settings are always false unless set.
30+
|Where multiple values are accepted, they should be comma-separated.
31+
| example: -Xplugin:plugin1,plugin2
32+
|<phases> means one or a comma-separated list of:
33+
| - (partial) phase names with an optional "+" suffix to include the next phase
34+
| - the string "all"
35+
| example: -Xprint:all prints all phases.
36+
| example: -Xprint:typer,mixin prints the typer and mixin phases.
37+
| example: -Ylog:erasure+ logs the erasure phase and the phase after the erasure phase.
38+
| This is useful because during the tree transform of phase X, we often
39+
| already are in phase X + 1.
40+
"""
41+
42+
/** Distill arguments into summary detailing settings, errors and files to main */
43+
def distill(args: Array[String], sg: Settings.SettingGroup)(ss: SettingsState = sg.defaultState)(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+
report.error(s"Argument file ${path.getFileName} could not be found")
53+
Nil
54+
else
55+
val lines = Files.readAllLines(path) // default to UTF-8 encoding
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, processAll = true, settingsState = ss)
66+
67+
/** Creates a help message for a subset of options based on cond */
68+
protected def availableOptionsMsg(cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
69+
val ss = (settings.allSettings filter cond).toList sortBy (_.name)
70+
val width = (ss map (_.name.length)).max
71+
def format(s: String) = ("%-" + width + "s") format s
72+
def helpStr(s: Setting[?]) =
73+
def defaultValue = s.default match
74+
case _: Int | _: String => s.default.toString
75+
case _ =>
76+
// For now, skip the default values that do not make sense for the end user.
77+
// For example 'false' for the version command.
78+
""
79+
80+
def formatSetting(name: String, value: String) =
81+
if (value.nonEmpty)
82+
// the format here is helping to make empty padding and put the additional information exactly under the description.
83+
s"\n${format("")} $name: $value."
84+
else
85+
""
86+
s"${format(s.name)} ${s.description}${formatSetting("Default", defaultValue)}${formatSetting("Choices", s.legalChoices)}"
87+
88+
ss.map(helpStr).mkString("", "\n", s"\n${format("@<file>")} A text file containing compiler arguments (options and source files).\n")
89+
90+
protected def shortUsage: String = s"Usage: $cmdName <options> <source files>"
91+
92+
protected def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
93+
val prefix = List(
94+
Some(shortUsage),
95+
Some(explainAdvanced) filter (_ => shouldExplain),
96+
Some(label + " options include:")
97+
).flatten mkString "\n"
98+
99+
prefix + "\n" + availableOptionsMsg(cond)
100+
101+
protected def isStandard(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean =
102+
!isAdvanced(s) && !isPrivate(s)
103+
protected def isAdvanced(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean =
104+
s.name.startsWith("-X") && s.name != "-X"
105+
protected def isPrivate(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean =
106+
s.name.startsWith("-Y") && s.name != "-Y"
107+
108+
/** Messages explaining usage and options */
109+
protected def usageMessage(using settings: ConcreteSettings)(using SettingsState) =
110+
createUsageMsg("where possible standard", shouldExplain = false, isStandard)
111+
protected def xusageMessage(using settings: ConcreteSettings)(using SettingsState) =
112+
createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced)
113+
protected def yusageMessage(using settings: ConcreteSettings)(using SettingsState) =
114+
createUsageMsg("Possible private", shouldExplain = true, isPrivate)
115+
116+
protected def phasesMessage: String =
117+
(new Compiler()).phases.map {
118+
case List(single) => single.phaseName
119+
case more => more.map(_.phaseName).mkString("{", ", ", "}")
120+
}.mkString("\n")
121+
122+
/** Provide usage feedback on argument summary, assuming that all settings
123+
* are already applied in context.
124+
* @return Either Some list of files passed as arguments or None if further processing should be interrupted.
125+
*/
126+
def checkUsage(summary: ArgsSummary, sourcesRequired: Boolean)(using settings: ConcreteSettings)(using SettingsState, Context): Option[List[String]] =
127+
// Print all warnings encountered during arguments parsing
128+
summary.warnings.foreach(report.warning(_))
129+
130+
if summary.errors.nonEmpty then
131+
summary.errors foreach (report.error(_))
132+
report.echo(ifErrorsMsg)
133+
None
134+
else if settings.version.value then
135+
report.echo(versionMsg)
136+
None
137+
else if isHelpFlag then
138+
report.echo(helpMsg)
139+
None
140+
else if (sourcesRequired && summary.arguments.isEmpty)
141+
report.echo(usageMessage)
142+
None
143+
else
144+
Some(summary.arguments)
145+
146+
extension [T](setting: Setting[T])
147+
protected def value(using ss: SettingsState): T = setting.valueIn(ss)

0 commit comments

Comments
 (0)