Skip to content

Commit 851a4fc

Browse files
authored
Print an informative error if the project workspace path contains File.pathSeparator (#1985)
1 parent 2fc0e07 commit 851a4fc

File tree

3 files changed

+103
-55
lines changed

3 files changed

+103
-55
lines changed

modules/build/src/main/scala/scala/build/input/Inputs.scala

Lines changed: 70 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package scala.build.input
22

3-
import java.io.ByteArrayInputStream
3+
import java.io.{ByteArrayInputStream, File}
44
import java.math.BigInteger
55
import java.nio.charset.StandardCharsets
66
import java.security.MessageDigest
77

88
import scala.annotation.tailrec
99
import scala.build.Directories
10-
import scala.build.errors.{BuildException, InputsException}
10+
import scala.build.errors.{BuildException, InputsException, WorkspaceError}
1111
import scala.build.input.ElementsUtils.*
1212
import scala.build.internal.Constants
1313
import scala.build.internal.zip.WrappedZipInputStream
@@ -125,53 +125,14 @@ object Inputs {
125125
private def forValidatedElems(
126126
validElems: Seq[Element],
127127
baseProjectName: String,
128-
forcedWorkspace: Option[os.Path],
128+
workspace: os.Path,
129+
needsHash: Boolean,
130+
workspaceOrigin: WorkspaceOrigin,
129131
enableMarkdown: Boolean,
130132
allowRestrictedFeatures: Boolean,
131133
extraClasspathWasPassed: Boolean
132134
): Inputs = {
133135
assert(extraClasspathWasPassed || validElems.nonEmpty)
134-
val (inferredWorkspace, inferredNeedsHash, workspaceOrigin) = {
135-
val settingsFiles = validElems.projectSettingsFiles
136-
val dirsAndFiles = validElems.collect {
137-
case d: Directory => d
138-
case f: SourceFile => f
139-
}
140-
settingsFiles.headOption.map { s =>
141-
if (settingsFiles.length > 1)
142-
System.err.println(
143-
s"Warning: more than one ${Constants.projectFileName} file has been found. Setting ${s.base} as the project root directory for this run."
144-
)
145-
(s.base, true, WorkspaceOrigin.SourcePaths)
146-
}.orElse {
147-
dirsAndFiles.collectFirst {
148-
case d: Directory =>
149-
if (dirsAndFiles.length > 1)
150-
System.err.println(
151-
s"Warning: setting ${d.path} as the project root directory for this run."
152-
)
153-
(d.path, true, WorkspaceOrigin.SourcePaths)
154-
case f: SourceFile =>
155-
if (dirsAndFiles.length > 1)
156-
System.err.println(
157-
s"Warning: setting ${f.path / os.up} as the project root directory for this run."
158-
)
159-
(f.path / os.up, true, WorkspaceOrigin.SourcePaths)
160-
}
161-
}.orElse {
162-
validElems.collectFirst {
163-
case _: Virtual =>
164-
(os.temp.dir(), true, WorkspaceOrigin.VirtualForced)
165-
}
166-
}.getOrElse((os.pwd, true, WorkspaceOrigin.Forced))
167-
}
168-
169-
val (workspace, needsHash, workspaceOrigin0) = forcedWorkspace match {
170-
case None => (inferredWorkspace, inferredNeedsHash, workspaceOrigin)
171-
case Some(forcedWorkspace0) =>
172-
val needsHash0 = forcedWorkspace0 != inferredWorkspace || inferredNeedsHash
173-
(forcedWorkspace0, needsHash0, WorkspaceOrigin.Forced)
174-
}
175136
val allDirs = validElems.collect { case d: Directory => d.path }
176137
val updatedElems = validElems.filter {
177138
case f: SourceFile =>
@@ -189,7 +150,7 @@ object Inputs {
189150
workspace,
190151
baseProjectName,
191152
mayAppendHash = needsHash,
192-
workspaceOrigin = Some(workspaceOrigin0),
153+
workspaceOrigin = Some(workspaceOrigin),
193154
enableMarkdown = enableMarkdown,
194155
allowRestrictedFeatures = allowRestrictedFeatures
195156
)
@@ -365,7 +326,7 @@ object Inputs {
365326
enableMarkdown: Boolean,
366327
allowRestrictedFeatures: Boolean,
367328
extraClasspathWasPassed: Boolean
368-
)(using ScalaCliInvokeData): Either[BuildException, Inputs] = {
329+
)(using invokeData: ScalaCliInvokeData): Either[BuildException, Inputs] = {
369330
val validatedArgs: Seq[Either[String, Seq[Element]]] =
370331
validateArgs(
371332
args,
@@ -386,15 +347,70 @@ object Inputs {
386347
case Right(elem) => elem
387348
}.flatten
388349
assert(extraClasspathWasPassed || validElems.nonEmpty)
350+
val (inferredWorkspace, inferredNeedsHash, workspaceOrigin) = {
351+
val settingsFiles = validElems.projectSettingsFiles
352+
val dirsAndFiles = validElems.collect {
353+
case d: Directory => d
354+
case f: SourceFile => f
355+
}
356+
settingsFiles.headOption.map { s =>
357+
if (settingsFiles.length > 1)
358+
System.err.println(
359+
s"Warning: more than one ${Constants.projectFileName} file has been found. Setting ${s.base} as the project root directory for this run."
360+
)
361+
(s.base, true, WorkspaceOrigin.SourcePaths)
362+
}.orElse {
363+
dirsAndFiles.collectFirst {
364+
case d: Directory =>
365+
if (dirsAndFiles.length > 1)
366+
System.err.println(
367+
s"Warning: setting ${d.path} as the project root directory for this run."
368+
)
369+
(d.path, true, WorkspaceOrigin.SourcePaths)
370+
case f: SourceFile =>
371+
if (dirsAndFiles.length > 1)
372+
System.err.println(
373+
s"Warning: setting ${f.path / os.up} as the project root directory for this run."
374+
)
375+
(f.path / os.up, true, WorkspaceOrigin.SourcePaths)
376+
}
377+
}.orElse {
378+
validElems.collectFirst {
379+
case _: Virtual =>
380+
(os.temp.dir(), true, WorkspaceOrigin.VirtualForced)
381+
}
382+
}.getOrElse((os.pwd, true, WorkspaceOrigin.Forced))
383+
}
389384

390-
Right(forValidatedElems(
391-
validElems,
392-
baseProjectName,
393-
forcedWorkspace,
394-
enableMarkdown,
395-
allowRestrictedFeatures,
396-
extraClasspathWasPassed
397-
))
385+
val (workspace, needsHash, workspaceOrigin0) = forcedWorkspace match {
386+
case None => (inferredWorkspace, inferredNeedsHash, workspaceOrigin)
387+
case Some(forcedWorkspace0) =>
388+
val needsHash0 = forcedWorkspace0 != inferredWorkspace || inferredNeedsHash
389+
(forcedWorkspace0, needsHash0, WorkspaceOrigin.Forced)
390+
}
391+
392+
if workspace.toString.contains(File.pathSeparator) then
393+
val prog = invokeData.invocationString
394+
val argsString = args.mkString(" ")
395+
Left(new WorkspaceError(
396+
s"""Invalid workspace path: ${Console.BOLD}$workspace${Console.RESET}
397+
|Workspace path cannot contain a ${Console.BOLD}${File.pathSeparator}${Console.RESET}.
398+
|Consider moving your project to a different path.
399+
|Alternatively, you can force your workspace with the '--workspace' option:
400+
| ${Console.BOLD}$prog --workspace <alternative-workspace-path> $argsString${Console.RESET}"""
401+
.stripMargin
402+
))
403+
else
404+
Right(forValidatedElems(
405+
validElems,
406+
baseProjectName,
407+
workspace,
408+
needsHash,
409+
workspaceOrigin0,
410+
enableMarkdown,
411+
allowRestrictedFeatures,
412+
extraClasspathWasPassed
413+
))
398414
}
399415
else
400416
Left(new InputsException(invalid.mkString(System.lineSeparator())))

modules/build/src/main/scala/scala/build/input/ScalaCliInvokeData.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ case class ScalaCliInvokeData(
1717
subCommandName: String,
1818
subCommand: SubCommand,
1919
isShebangCapableShell: Boolean
20-
)
20+
) {
21+
22+
/** [[progName]] with [[subCommandName]] if any */
23+
def invocationString: String =
24+
subCommand match
25+
case SubCommand.Default => progName
26+
case _ => s"$progName $subCommandName"
27+
}
2128

2229
enum SubCommand:
2330
case Default extends SubCommand

modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,31 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
11291129
}
11301130
}
11311131

1132+
test(s"print error if workspace path contains a ${File.pathSeparator}") {
1133+
val msg = "Hello"
1134+
val relPath = os.rel / s"weird${File.pathSeparator}directory" / "Hello.scala"
1135+
TestInputs(
1136+
relPath ->
1137+
s"""object Hello extends App {
1138+
| println("$msg")
1139+
|}
1140+
|""".stripMargin
1141+
)
1142+
.fromRoot { root =>
1143+
val resWithColon =
1144+
os.proc(TestUtil.cli, "run", relPath.toString, extraOptions)
1145+
.call(cwd = root, check = false, stderr = os.Pipe)
1146+
expect(resWithColon.exitCode == 1)
1147+
expect(resWithColon.err.trim().contains(
1148+
"you can force your workspace with the '--workspace' option:"
1149+
))
1150+
val resFixedWorkspace = // should run fine for a forced workspace with no classpath separator on path
1151+
os.proc(TestUtil.cli, "run", relPath.toString, "--workspace", ".", extraOptions)
1152+
.call(cwd = root)
1153+
expect(resFixedWorkspace.out.trim() == msg)
1154+
}
1155+
}
1156+
11321157
val commands = Seq("", "run", "compile")
11331158

11341159
for (command <- commands) {

0 commit comments

Comments
 (0)