Skip to content

Commit 0e3507e

Browse files
authored
Merge pull request #13619 from philwalk/unglob-windows-wildcard-classpath
fix 13618 - undo windows wildcard classpath globbing
2 parents 01c70a2 + bf753c7 commit 0e3507e

File tree

5 files changed

+68
-28
lines changed

5 files changed

+68
-28
lines changed

compiler/src/dotty/tools/MainGenericRunner.scala

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,20 @@ object MainGenericRunner {
105105
case "-run" :: fqName :: tail =>
106106
process(tail, settings.withExecuteMode(ExecuteMode.Run).withTargetToRun(fqName))
107107
case ("-cp" | "-classpath" | "--class-path") :: cp :: tail =>
108-
val globdir = cp.replaceAll("[\\/][^\\/]*$", "") // slash/backslash agnostic
109-
val (tailargs, cpstr) = if globdir.nonEmpty && classpathSeparator != ";" || cp.contains(classpathSeparator) then
110-
(tail, cp)
108+
val cpEntries = cp.split(classpathSeparator).toList
109+
val singleEntryClasspath: Boolean = cpEntries.take(2).size == 1
110+
val globdir: String = if singleEntryClasspath then cp.replaceAll("[\\\\/][^\\\\/]*$", "") else "" // slash/backslash agnostic
111+
def validGlobbedJar(s: String): Boolean = s.startsWith(globdir) && ((s.toLowerCase.endsWith(".jar") || s.toLowerCase.endsWith(".zip")))
112+
val (tailargs, newEntries) = if singleEntryClasspath && validGlobbedJar(cpEntries.head) then
113+
// reassemble globbed wildcard classpath
114+
// globdir is wildcard directory for globbed jar files, reconstruct the intended classpath
115+
val cpJars = tail.takeWhile( f => validGlobbedJar(f) )
116+
val remainingArgs = tail.drop(cpJars.size)
117+
(remainingArgs, cpEntries ++ cpJars)
111118
else
112-
// combine globbed classpath entries into a classpath
113-
val jarfiles = cp :: tail
114-
val cpfiles = jarfiles.takeWhile( f => f.startsWith(globdir) && ((f.toLowerCase.endsWith(".jar") || f.endsWith(".zip"))) )
115-
val tailargs = jarfiles.drop(cpfiles.size)
116-
(tailargs, cpfiles.mkString(classpathSeparator))
117-
118-
process(tailargs, settings.copy(classPath = settings.classPath ++ cpstr.split(classpathSeparator).filter(_.nonEmpty)))
119+
(tail, cpEntries)
120+
121+
process(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty)))
119122

120123
case ("-version" | "--version") :: _ =>
121124
settings.copy(
@@ -204,16 +207,15 @@ object MainGenericRunner {
204207
val targetScript = Paths.get(settings.targetScript).toFile
205208
val targetJar = settings.targetScript.replaceAll("[.][^\\/]*$", "")+".jar"
206209
val precompiledJar = Paths.get(targetJar).toFile
207-
def mainClass = Jar(targetJar).mainClass.getOrElse("") // throws exception if file not found
208-
val jarIsValid = precompiledJar.isFile && mainClass.nonEmpty && precompiledJar.lastModified >= targetScript.lastModified
210+
val mainClass = if !precompiledJar.isFile then "" else Jar(targetJar).mainClass.getOrElse("")
211+
val jarIsValid = mainClass.nonEmpty && precompiledJar.lastModified >= targetScript.lastModified
209212
if jarIsValid then
210213
// precompiledJar exists, is newer than targetScript, and manifest defines a mainClass
211214
sys.props("script.path") = targetScript.toPath.toAbsolutePath.normalize.toString
212215
val scalaClasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator)
213216
val newClasspath = (settings.classPath.flatMap(_.split(classpathSeparator).filter(_.nonEmpty)) ++ removeCompiler(scalaClasspath) :+ ".").map(File(_).toURI.toURL)
214-
val mc = mainClass
215-
if mc.nonEmpty then
216-
ObjectRunner.runAndCatch(newClasspath :+ File(targetJar).toURI.toURL, mc, settings.scriptArgs)
217+
if mainClass.nonEmpty then
218+
ObjectRunner.runAndCatch(newClasspath :+ File(targetJar).toURI.toURL, mainClass, settings.scriptArgs)
217219
else
218220
Some(IllegalArgumentException(s"No main class defined in manifest in jar: $precompiledJar"))
219221
else
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!bin/scala -classpath 'dist/target/pack/lib/*'
2+
3+
// won't compile unless the hashbang line sets classpath
4+
import org.jline.terminal.Terminal
5+
6+
def main(args: Array[String]) =
7+
val cp = sys.props("java.class.path")
8+
printf("unglobbed classpath: %s\n", cp)

compiler/test/dotty/tools/scripting/BashScriptsTests.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,13 @@ class BashScriptsTests:
9797
printf("===> verify SCALA_OPTS='@argsfile' is properly handled by `dist/bin/scala`\n")
9898
val envPairs = List(("SCALA_OPTS", s"@$argsfile"))
9999
val (validTest, exitCode, stdout, stderr) = bashCommand(scriptFile.absPath, envPairs)
100+
printf("stdout: %s\n", stdout.mkString("\n","\n",""))
100101
if validTest then
101-
val expected = s"${workingDirectory.toString}"
102-
val List(line1: String, line2: String) = stdout.take(2)
103-
printf("line1 [%s]\n", line1)
104-
val valid = line2.dropWhile( _ != ' ').trim.startsWith(expected)
102+
val expected = s"${workingDirectory.norm}"
103+
val output = stdout.find( _.trim.startsWith("cwd") ).getOrElse("").dropWhile(_!=' ').trim
104+
printf("output [%s]\n", output)
105+
printf("expected[%s]\n", expected)
106+
val valid = output.startsWith(expected)
105107
if valid then printf(s"\n===> success: classpath begins with %s, as reported by [%s]\n", workingDirectory, scriptFile.getName)
106108
assert(valid, s"script ${scriptFile.absPath} did not report valid java.class.path first entry")
107109

compiler/test/dotty/tools/scripting/ClasspathTests.scala

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,19 @@ class ClasspathTests:
1818
val packBinDir = "dist/target/pack/bin"
1919
val packLibDir = "dist/target/pack/lib"
2020

21-
// only interested in classpath test scripts
22-
val testScriptName = "classpathReport.sc"
23-
val testScript = scripts("/scripting").find { _.getName.matches(testScriptName) } match
24-
case None => sys.error(s"test script not found: ${testScriptName}")
25-
case Some(file) => file
26-
2721
def exists(scriptPath: Path): Boolean = Files.exists(scriptPath)
2822
def packBinScalaExists:Boolean = exists(Paths.get(s"$packBinDir/scala"))
2923

3024
/*
3125
* verify classpath reported by called script.
3226
*/
3327
@Test def hashbangClasspathVerifyTest = {
28+
// only interested in classpath test scripts
29+
val testScriptName = "classpathReport.sc"
30+
val testScript = scripts("/scripting").find { _.getName.matches(testScriptName) } match
31+
case None => sys.error(s"test script not found: ${testScriptName}")
32+
case Some(file) => file
33+
3434
val relpath = testScript.toPath.relpath.norm
3535
printf("===> hashbangClasspathVerifyTest for script [%s]\n", relpath)
3636
printf("bash is [%s]\n", bashExe)
@@ -50,13 +50,41 @@ class ClasspathTests:
5050
val hashbangClasspathJars = scriptCp.split(psep).map { _.getName }.sorted.distinct
5151
val packlibJars = listJars(s"$scriptCwd/$packLibDir").sorted.distinct
5252

53+
printf("%d jar files in dist/target/pack/lib\n", packlibJars.size)
54+
printf("%d test script jars in classpath\n", hashbangClasspathJars.size)
55+
5356
// verify that the classpath set in the hashbang line is effective
5457
if hashbangClasspathJars.size != packlibJars.size then
55-
printf("%d test script jars in classpath\n", hashbangClasspathJars.size)
56-
printf("%d jar files in dist/target/pack/lib\n", packlibJars.size)
58+
printf("hashbangClasspathJars: %s\n", hashbangClasspathJars.mkString("\n ", "\n ", ""))
5759

5860
assert(hashbangClasspathJars.size == packlibJars.size)
5961
}
62+
/*
63+
* verify classpath is unglobbed by MainGenericRunner.
64+
*/
65+
@Test def unglobClasspathVerifyTest = {
66+
val testScriptName = "unglobClasspath.sc"
67+
val testScript = scripts("/scripting").find { _.getName.matches(testScriptName) } match
68+
case None => sys.error(s"test script not found: ${testScriptName}")
69+
case Some(file) => file
70+
71+
val relpath = testScript.toPath.relpath.norm
72+
printf("===> unglobClasspathVerifyTest for script [%s]\n", relpath)
73+
printf("bash is [%s]\n", bashExe)
74+
75+
if packBinScalaExists then
76+
val bashCmdline = s"SCALA_OPTS= $relpath"
77+
val cmd = Array(bashExe, "-c", bashCmdline)
78+
79+
cmd.foreach { printf("[%s]\n", _) }
80+
81+
// test script reports the classpath it sees
82+
val scriptOutput = exec(cmd:_*)
83+
val scriptCp = findTaggedLine("unglobbed classpath", scriptOutput)
84+
val classpathJars = scriptCp.split(psep).map { _.getName }.sorted.distinct
85+
//classpathJars.foreach { printf("%s\n", _) }
86+
assert(classpathJars.size > 1)
87+
}
6088

6189

6290
//////////////// end of tests ////////////////

compiler/test/dotty/tools/scripting/ScriptingTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ScriptingTests:
1919
f.getAbsolutePath.replace('\\', '/')
2020

2121
// classpath tests managed by scripting.ClasspathTests.scala
22-
def testFiles = scripts("/scripting").filter { ! _.getName.startsWith("classpath") }
22+
def testFiles = scripts("/scripting").filter { ! _.getName.toLowerCase.contains("classpath") }
2323

2424
def script2jar(scriptFile: File) =
2525
val jarName = s"${scriptFile.getName.dropExtension}.jar"

0 commit comments

Comments
 (0)