Skip to content

Commit a8a0a61

Browse files
committed
Swap @main error with 'unsupported by Scala CLI'
1 parent ed50769 commit a8a0a61

File tree

9 files changed

+243
-29
lines changed

9 files changed

+243
-29
lines changed

modules/build/src/main/scala/scala/build/Build.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,7 @@ object Build {
10811081
options.notForBloopOptions.packageOptions.packageTypeOpt.exists(_.sourceBased)
10821082
}
10831083

1084-
val success = partial || compiler.compile(project, logger)
1084+
val success = partial || compiler.compile(project, logger, generatedSources)
10851085

10861086
if (success)
10871087
Successful(

modules/build/src/main/scala/scala/build/ConsoleBloopBuildClient.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import java.nio.file.Paths
88

99
import scala.build.errors.Severity
1010
import scala.build.internal.util.ConsoleUtils.ScalaCliConsole
11+
import scala.build.internal.util.ErrorMessages
1112
import scala.build.options.Scope
1213
import scala.collection.mutable
1314
import scala.jdk.CollectionConverters.*
@@ -58,7 +59,18 @@ class ConsoleBloopBuildClient(
5859
)
5960
val range = new bsp4j.Range(start, end)
6061

61-
val updatedDiag = new bsp4j.Diagnostic(range, diag.getMessage)
62+
val message =
63+
if (
64+
diag.getMessage.contains(
65+
"cannot be a main method since it cannot be accessed statically"
66+
) &&
67+
(originalPath.exists(_.ext == "sc") || originalPath.left.exists(_ == "snippet"))
68+
)
69+
ErrorMessages.mainAnnotationNotSupported
70+
else
71+
diag.getMessage
72+
73+
val updatedDiag = new bsp4j.Diagnostic(range, message)
6274
updatedDiag.setCode(diag.getCode)
6375
updatedDiag.setRelatedInformation(diag.getRelatedInformation)
6476
updatedDiag.setSeverity(diag.getSeverity)

modules/build/src/main/scala/scala/build/bsp/BspClient.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package scala.build.bsp
22

3-
import ch.epfl.scala.{bsp4j => b}
3+
import ch.epfl.scala.bsp4j as b
44

55
import java.lang.Boolean as JBoolean
66
import java.net.URI
@@ -10,6 +10,7 @@ import java.util.concurrent.{ConcurrentHashMap, ExecutorService}
1010
import scala.build.Position.File
1111
import scala.build.bsp.protocol.TextEdit
1212
import scala.build.errors.{BuildException, CompositeBuildException, Diagnostic, Severity}
13+
import scala.build.internal.util.ErrorMessages
1314
import scala.build.postprocessing.LineConversion
1415
import scala.build.{BloopBuildClient, GeneratedSource, Logger}
1516
import scala.jdk.CollectionConverters.*
@@ -48,6 +49,17 @@ class BspClient(
4849
val diag0 = diag.duplicate()
4950
diag0.getRange.getStart.setLine(startLine)
5051
diag0.getRange.getEnd.setLine(endLine)
52+
53+
if (
54+
diag.getMessage.contains(
55+
"cannot be a main method since it cannot be accessed statically"
56+
) &&
57+
(genSource.reportingPath.exists(
58+
_.ext == "sc"
59+
) || genSource.reportingPath.left.exists(_ == "snippet"))
60+
)
61+
diag0.setMessage(ErrorMessages.mainAnnotationNotSupported)
62+
5163
diag0
5264
}
5365
updatedDiagOpt.getOrElse(diag)

modules/build/src/main/scala/scala/build/compiler/BloopCompiler.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package scala.build.compiler
33
import ch.epfl.scala.bsp4j
44

55
import scala.annotation.tailrec
6-
import scala.build.{Bloop, Logger, Position, Positioned, Project}
6+
import scala.build.{Bloop, GeneratedSource, Logger, Position, Positioned, Project}
77
import scala.concurrent.duration.FiniteDuration
88

99
final class BloopCompiler(
@@ -32,7 +32,8 @@ final class BloopCompiler(
3232

3333
def compile(
3434
project: Project,
35-
logger: Logger
35+
logger: Logger,
36+
generatedSources: Seq[GeneratedSource]
3637
): Boolean = {
3738
@tailrec
3839
def helper(remainingAttempts: Int): Boolean =

modules/build/src/main/scala/scala/build/compiler/ScalaCompiler.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package scala.build.compiler
22

3-
import scala.build.{Logger, Positioned, Project}
3+
import scala.build.{GeneratedSource, Logger, Positioned, Project}
44

55
trait ScalaCompiler {
66
def jvmVersion: Option[Positioned[Int]]
@@ -10,7 +10,8 @@ trait ScalaCompiler {
1010
): Boolean
1111
def compile(
1212
project: Project,
13-
logger: Logger
13+
logger: Logger,
14+
generatedSources: Seq[GeneratedSource]
1415
): Boolean
1516
def shutdown(): Unit
1617

@@ -40,10 +41,11 @@ object ScalaCompiler {
4041
compiler.prepareProject(project, logger)
4142
def compile(
4243
project: Project,
43-
logger: Logger
44+
logger: Logger,
45+
generatedSources: Seq[GeneratedSource]
4446
): Boolean =
4547
ignore(project, logger) ||
46-
compiler.compile(project, logger)
48+
compiler.compile(project, logger, generatedSources)
4749
def shutdown(): Unit =
4850
compiler.shutdown()
4951
override def usesClassDir: Boolean =

modules/build/src/main/scala/scala/build/compiler/SimpleScalaCompiler.scala

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package scala.build.compiler
22

3-
import java.io.File
3+
import java.io.{BufferedInputStream, File}
44

55
import scala.build.internal.Runner
6-
import scala.build.{Logger, Positioned, Project}
6+
import scala.build.internal.util.ErrorMessages
7+
import scala.build.postprocessing.LineConversion
8+
import scala.build.{GeneratedSource, Logger, Positioned, Project}
9+
import scala.jdk.CollectionConverters.*
710

811
/** A simple Scala compiler designed to handle scaladocs, Java projects & get `scalac` outputs.
912
*
@@ -68,7 +71,8 @@ final case class SimpleScalaCompiler(
6871
sources: Seq[String],
6972
outputDir: Option[os.Path],
7073
cwd: os.Path,
71-
logger: Logger
74+
logger: Logger,
75+
generatedSources: Seq[GeneratedSource]
7276
): Int = {
7377

7478
outputDir.foreach(os.makeDir.all(_))
@@ -90,15 +94,65 @@ final case class SimpleScalaCompiler(
9094
.filter(_.startsWith("-J"))
9195
.map(_.stripPrefix("-J"))
9296

93-
Runner.runJvm(
97+
val compileProcess = Runner.runJvm(
9498
javaCommand,
9599
javaOptions,
96100
compilerClassPath,
97101
mainClass,
98102
args,
99103
logger,
100-
cwd = Some(cwd)
101-
).waitFor()
104+
cwd = Some(cwd),
105+
redirectStdErr = true
106+
)
107+
108+
if (mainClass == "dotty.tools.dotc.Main") {
109+
val processErrorStream = new BufferedInputStream(compileProcess.getErrorStream)
110+
val errorOutput = new String(processErrorStream.readAllBytes())
111+
112+
val errorLines = errorOutput.linesIterator.toList
113+
114+
val errorBeginRegex = "^.*--.*Error: (.*):\\d+:\\d+.*".r
115+
val mainAnnotMsg = "cannot be a main method since it cannot be accessed statically"
116+
117+
val generatedSourcesToOrigin = generatedSources
118+
.map(gs => (gs.generated, gs.reportingPath))
119+
.toMap
120+
121+
def maybeSwapAndPrint(errorLines: List[String], wasErrorInScript: Boolean = false): Unit =
122+
errorLines match {
123+
case h :: t => h match {
124+
case errorBeginRegex(path) => generatedSourcesToOrigin.get(os.Path(path)) match {
125+
case Some(Left(origin)) =>
126+
System.err.println(h.replace(path, origin))
127+
maybeSwapAndPrint(t, origin == "snippet")
128+
case Some(Right(originPath)) =>
129+
System.err.println(h.replace(path, originPath.toString))
130+
maybeSwapAndPrint(t, originPath.ext == "sc")
131+
case _ =>
132+
System.err.println(h)
133+
maybeSwapAndPrint(t)
134+
}
135+
case l if l.contains(mainAnnotMsg) && wasErrorInScript =>
136+
System.err.println(
137+
l.take(l.indexOf('|') + 1 + Console.RESET.size) +
138+
ErrorMessages.mainAnnotationNotSupported
139+
)
140+
maybeSwapAndPrint(t)
141+
case l =>
142+
System.err.println(l)
143+
maybeSwapAndPrint(t, wasErrorInScript)
144+
}
145+
case Nil => ()
146+
}
147+
148+
maybeSwapAndPrint(errorLines)
149+
}
150+
else {
151+
val processErrorStream = new BufferedInputStream(compileProcess.getErrorStream)
152+
processErrorStream.transferTo(System.err)
153+
}
154+
155+
compileProcess.waitFor()
102156
}
103157

104158
/** Run a synthetic (created in runtime) `scalac` as a JVM process for a given
@@ -119,7 +173,8 @@ final case class SimpleScalaCompiler(
119173
project: Project,
120174
mainClass: String,
121175
outputDir: os.Path,
122-
logger: Logger
176+
logger: Logger,
177+
generatedSources: Seq[GeneratedSource]
123178
): Boolean = {
124179
val res = runScalacLike(
125180
mainClass = mainClass,
@@ -131,7 +186,8 @@ final case class SimpleScalaCompiler(
131186
sources = project.sources.map(_.toString),
132187
outputDir = Some(outputDir),
133188
cwd = project.workspace,
134-
logger = logger
189+
logger = logger,
190+
generatedSources = generatedSources
135191
)
136192
res == 0
137193
}
@@ -177,7 +233,8 @@ final case class SimpleScalaCompiler(
177233
sources = Nil,
178234
outputDir = None,
179235
cwd = os.pwd,
180-
logger = logger
236+
logger = logger,
237+
generatedSources = Nil
181238
)
182239
case _ => 1
183240
}
@@ -193,7 +250,8 @@ final case class SimpleScalaCompiler(
193250

194251
def compile(
195252
project: Project,
196-
logger: Logger
253+
logger: Logger,
254+
generatedSources: Seq[GeneratedSource]
197255
): Boolean =
198256
if (project.sources.isEmpty) true
199257
else
@@ -206,7 +264,7 @@ final case class SimpleScalaCompiler(
206264
if (isScala2 && scaladoc) project.scaladocDir
207265
else project.classesDir
208266

209-
runScalacLikeForProject(project, mainClass, outputDir, logger)
267+
runScalacLikeForProject(project, mainClass, outputDir, logger, generatedSources)
210268
}
211269

212270
case None =>

modules/build/src/main/scala/scala/build/internal/Runner.scala

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.scalajs.jsenv.{Input, RunConfig}
77
import sbt.testing.{Framework, Status}
88

99
import java.io.File
10+
import java.lang.ProcessBuilder.Redirect
1011
import java.nio.file.{Files, Path, Paths}
1112

1213
import scala.build.EitherCps.{either, value}
@@ -28,30 +29,34 @@ object Runner {
2829
command: Seq[String],
2930
logger: Logger,
3031
cwd: Option[os.Path] = None,
31-
extraEnv: Map[String, String] = Map.empty
32+
extraEnv: Map[String, String] = Map.empty,
33+
redirectStdErr: Boolean = false
3234
): Process =
3335
run0(
3436
commandName,
3537
command,
3638
logger,
3739
allowExecve = true,
3840
cwd,
39-
extraEnv
41+
extraEnv,
42+
redirectStdErr
4043
)
4144

4245
def run(
4346
command: Seq[String],
4447
logger: Logger,
4548
cwd: Option[os.Path] = None,
46-
extraEnv: Map[String, String] = Map.empty
49+
extraEnv: Map[String, String] = Map.empty,
50+
redirectStdErr: Boolean = false
4751
): Process =
4852
run0(
4953
"unused",
5054
command,
5155
logger,
5256
allowExecve = false,
5357
cwd,
54-
extraEnv
58+
extraEnv,
59+
redirectStdErr
5560
)
5661

5762
def run0(
@@ -60,7 +65,8 @@ object Runner {
6065
logger: Logger,
6166
allowExecve: Boolean,
6267
cwd: Option[os.Path],
63-
extraEnv: Map[String, String]
68+
extraEnv: Map[String, String],
69+
redirectStdErr: Boolean = false
6470
): Process = {
6571

6672
import logger.{log, debug}
@@ -86,7 +92,9 @@ object Runner {
8692
}
8793
else {
8894
val b = new ProcessBuilder(command: _*)
89-
.inheritIO()
95+
.redirectInput(Redirect.INHERIT)
96+
.redirectOutput(Redirect.INHERIT)
97+
.redirectError(if redirectStdErr then Redirect.PIPE else Redirect.INHERIT)
9098
if (extraEnv.nonEmpty) {
9199
val env = b.environment()
92100
for ((k, v) <- extraEnv)
@@ -158,7 +166,8 @@ object Runner {
158166
cwd: Option[os.Path] = None,
159167
extraEnv: Map[String, String] = Map.empty,
160168
useManifest: Option[Boolean] = None,
161-
scratchDirOpt: Option[os.Path] = None
169+
scratchDirOpt: Option[os.Path] = None,
170+
redirectStdErr: Boolean = false
162171
): Process = {
163172

164173
val command = jvmCommand(
@@ -173,9 +182,16 @@ object Runner {
173182
)
174183

175184
if (allowExecve)
176-
maybeExec("java", command, logger, cwd = cwd, extraEnv = extraEnv)
185+
maybeExec(
186+
"java",
187+
command,
188+
logger,
189+
cwd = cwd,
190+
extraEnv = extraEnv,
191+
redirectStdErr = redirectStdErr
192+
)
177193
else
178-
run(command, logger, cwd = cwd, extraEnv = extraEnv)
194+
run(command, logger, cwd = cwd, extraEnv = extraEnv, redirectStdErr = redirectStdErr)
179195
}
180196

181197
private def endsWithCaseInsensitive(s: String, suffix: String): Boolean =
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package scala.build.internal.util
2+
3+
object ErrorMessages {
4+
5+
/** Using @main is impossible in new [[scala.build.internal.ClassCodeWrapper]] since none of the
6+
* definitions can be accessed statically, so those errors are swapped with this text
7+
*/
8+
val mainAnnotationNotSupported: String =
9+
"Scala CLI does not support @main in .sc scripts, use .scala format instead"
10+
}

0 commit comments

Comments
 (0)