/
CompilerCommand.scala
148 lines (127 loc) · 5.93 KB
/
CompilerCommand.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.tools.nsc
/** A class representing command line info for scalac */
class CompilerCommand(arguments: List[String], val settings: Settings) {
def this(arguments: List[String], error: String => Unit) = this(arguments, new Settings(error))
def this(arguments: List[String], settings: Settings, error: String => Unit) = this(arguments, settings withErrorFn error)
type Setting = Settings#Setting
private val processArgumentsResult =
if (shouldProcessArguments) processArguments
else (true, Nil)
def ok = processArgumentsResult._1
def files = processArgumentsResult._2
/** The name of the command. */
def cmdName = "scalac"
/** A descriptive alias for version and help messages. */
def cmdDesc = "compiler"
private def explainAdvanced = """
|-- Note --
|Boolean settings generally are false unless set: -Xdev -Xcheck-init:true -Xprompt:false
|Multi-valued settings are comma-separated: -Xlint:infer-any,unused,-missing-interpolator
|Phases are a list of names, ids, or ranges of ids: -Vprint:parser,typer,5-10 -Ylog:-4
|Use _ to enable all: -language:_ -Vprint:_
|
""".stripMargin.trim
def shortUsage = "Usage: %s <options> <source files>" format cmdName
/** Creates a help message for a subset of options based on cond */
def optionsMessage(cond: Setting => Boolean): String = {
val iswarning = cond(settings.warnUnused) // sordid check for if we're building -W warning help, to include lint and unused
val baseList = settings.visibleSettings.filter(cond).toList.sortBy(_.name)
val (deprecateds, theRest) = baseList.partition(_.isDeprecated)
def columnOneWidth(s: Setting): Int =
if (iswarning && (s == settings.lint || s == settings.warnUnused))
s.asInstanceOf[settings.MultiChoiceSetting[_]].choices.map(c => s"${s.name}:$c".length).max
else
s.helpSyntax.length
val width = baseList.map(columnOneWidth).max
val columnOneFormat = s"%-${width}s"
def format(s: String) = columnOneFormat.format(s)
def layout(c1: String, c2: String) = s"${format(c1)} ${c2}"
def helpStr(s: Setting) = {
val str = layout(s.helpSyntax, s.helpDescription)
val suffix = s.deprecationMessage match {
case Some(msg) => "\n" + format("") + " deprecated: " + msg
case _ => ""
}
str + suffix
}
def appendDescriptions(sb: StringBuilder, msg: String, xs: List[Setting]): Unit =
if (!xs.isEmpty) {
val ss = xs.flatMap { s =>
if (iswarning && (s == settings.lint || s == settings.warnUnused)) {
val mcs = s.asInstanceOf[settings.MultiChoiceSetting[_]]
mcs.choices.map(c => s"${s.name}:$c").zipAll(mcs.descriptions, "", "").map {
case (c, d) => layout(c, d)
}
} else
List(helpStr(s))
}
sb.append(msg)
for (each <- ss) sb.append(" ").append(each).append("\n")
}
val sb = new StringBuilder()
appendDescriptions(sb, "", theRest)
appendDescriptions(sb, "\nDeprecated settings:\n", deprecateds)
sb.toString
}
def createUsageMsg(label: String, explain: Boolean = true)(cond: Setting => Boolean): String = {
val explained = if (explain) s"\n$explainAdvanced" else ""
s"$shortUsage\n\n$label options:\n${optionsMessage(cond)}${explained}\n"
}
/** Messages explaining usage and options */
def usageMsg = createUsageMsg("Standard", explain = false)(_.isStandard)
def vusageMsg = createUsageMsg("Verbose")(_.isVerbose)
def wusageMsg = createUsageMsg("Warnings")(_.isWarning)
def xusageMsg = createUsageMsg("Available advanced")(_.isAdvanced)
def yusageMsg = createUsageMsg("Available private")(_.isPrivate)
/** For info settings, compiler should just print a message and quit. */
def shouldStopWithInfo = settings.isInfo
def getInfoMessage(global: Global): String = {
import settings._
if (version.value) Properties.versionFor(cmdDesc)
else if (help.value) usageMsg + global.pluginOptionsHelp
else if (Vhelp.value) vusageMsg
else if (Whelp.value) wusageMsg
else if (Xhelp.value) xusageMsg
else if (Yhelp.value) yusageMsg
else if (showPlugins.value) global.pluginDescriptions
else if (showPhases.value) global.phaseDescriptions + (
if (settings.isDebug) "\n" + global.phaseFlagDescriptions else ""
)
else if (genPhaseGraph.isSetByUser) {
val components = global.phaseNames // global.phaseDescriptors // one initializes
s"Phase graph of ${components.size} components output to ${genPhaseGraph.value}*.dot."
}
else allSettings.valuesIterator.filter(_.isHelping).map(_.help).mkString("\n\n")
}
/** Expands all arguments starting with @ to the contents of the file named like each argument. */
def expandArg(arg: String): List[String] = {
import java.nio.file.{Files, Paths}
import scala.jdk.CollectionConverters._
def stripComment(s: String) = s.takeWhile(_ != '#')
val file = Paths.get(arg stripPrefix "@")
if (!Files.exists(file))
throw new java.io.FileNotFoundException(s"argument file $file could not be found")
settings.splitParams(Files.readAllLines(file).asScala.map(stripComment).mkString(" "))
}
// override this if you don't want arguments processed here
def shouldProcessArguments: Boolean = true
def processArguments: (Boolean, List[String]) = {
// expand out @filename to the contents of that filename
val expandedArguments = arguments flatMap {
case x if x startsWith "@" => expandArg(x)
case x => List(x)
}
settings.processArguments(expandedArguments, processAll = true)
}
}