diff --git a/compiler/src/dotty/tools/MainGenericRunner.scala b/compiler/src/dotty/tools/MainGenericRunner.scala index fa6faa7d570e..dfc56a8c11d2 100644 --- a/compiler/src/dotty/tools/MainGenericRunner.scala +++ b/compiler/src/dotty/tools/MainGenericRunner.scala @@ -105,17 +105,20 @@ object MainGenericRunner { case "-run" :: fqName :: tail => process(tail, settings.withExecuteMode(ExecuteMode.Run).withTargetToRun(fqName)) case ("-cp" | "-classpath" | "--class-path") :: cp :: tail => - val globdir = cp.replaceAll("[\\/][^\\/]*$", "") // slash/backslash agnostic - val (tailargs, cpstr) = if globdir.nonEmpty && classpathSeparator != ";" || cp.contains(classpathSeparator) then - (tail, cp) + val cpEntries = cp.split(classpathSeparator).toList + val singleEntryClasspath: Boolean = cpEntries.take(2).size == 1 + val globdir: String = if singleEntryClasspath then cp.replaceAll("[\\\\/][^\\\\/]*$", "") else "" // slash/backslash agnostic + def validGlobbedJar(s: String): Boolean = s.startsWith(globdir) && ((s.toLowerCase.endsWith(".jar") || s.toLowerCase.endsWith(".zip"))) + val (tailargs, newEntries) = if singleEntryClasspath && validGlobbedJar(cpEntries.head) then + // reassemble globbed wildcard classpath + // globdir is wildcard directory for globbed jar files, reconstruct the intended classpath + val cpJars = tail.takeWhile( f => validGlobbedJar(f) ) + val remainingArgs = tail.drop(cpJars.size) + (remainingArgs, cpEntries ++ cpJars) else - // combine globbed classpath entries into a classpath - val jarfiles = cp :: tail - val cpfiles = jarfiles.takeWhile( f => f.startsWith(globdir) && ((f.toLowerCase.endsWith(".jar") || f.endsWith(".zip"))) ) - val tailargs = jarfiles.drop(cpfiles.size) - (tailargs, cpfiles.mkString(classpathSeparator)) - - process(tailargs, settings.copy(classPath = settings.classPath ++ cpstr.split(classpathSeparator).filter(_.nonEmpty))) + (tail, cpEntries) + + process(tailargs, settings.copy(classPath = settings.classPath ++ newEntries.filter(_.nonEmpty))) case ("-version" | "--version") :: _ => settings.copy( @@ -204,16 +207,15 @@ object MainGenericRunner { val targetScript = Paths.get(settings.targetScript).toFile val targetJar = settings.targetScript.replaceAll("[.][^\\/]*$", "")+".jar" val precompiledJar = Paths.get(targetJar).toFile - def mainClass = Jar(targetJar).mainClass.getOrElse("") // throws exception if file not found - val jarIsValid = precompiledJar.isFile && mainClass.nonEmpty && precompiledJar.lastModified >= targetScript.lastModified + val mainClass = if !precompiledJar.isFile then "" else Jar(targetJar).mainClass.getOrElse("") + val jarIsValid = mainClass.nonEmpty && precompiledJar.lastModified >= targetScript.lastModified if jarIsValid then // precompiledJar exists, is newer than targetScript, and manifest defines a mainClass sys.props("script.path") = targetScript.toPath.toAbsolutePath.normalize.toString val scalaClasspath = ClasspathFromClassloader(Thread.currentThread().getContextClassLoader).split(classpathSeparator) val newClasspath = (settings.classPath.flatMap(_.split(classpathSeparator).filter(_.nonEmpty)) ++ removeCompiler(scalaClasspath) :+ ".").map(File(_).toURI.toURL) - val mc = mainClass - if mc.nonEmpty then - ObjectRunner.runAndCatch(newClasspath :+ File(targetJar).toURI.toURL, mc, settings.scriptArgs) + if mainClass.nonEmpty then + ObjectRunner.runAndCatch(newClasspath :+ File(targetJar).toURI.toURL, mainClass, settings.scriptArgs) else Some(IllegalArgumentException(s"No main class defined in manifest in jar: $precompiledJar")) else diff --git a/compiler/test-resources/scripting/unglobClasspath.sc b/compiler/test-resources/scripting/unglobClasspath.sc new file mode 100755 index 000000000000..796697cdedf2 --- /dev/null +++ b/compiler/test-resources/scripting/unglobClasspath.sc @@ -0,0 +1,8 @@ +#!bin/scala -classpath 'dist/target/pack/lib/*' + +// won't compile unless the hashbang line sets classpath +import org.jline.terminal.Terminal + +def main(args: Array[String]) = + val cp = sys.props("java.class.path") + printf("unglobbed classpath: %s\n", cp) diff --git a/compiler/test/dotty/tools/scripting/BashScriptsTests.scala b/compiler/test/dotty/tools/scripting/BashScriptsTests.scala index 8a6c6aad1ec1..061350e86e33 100644 --- a/compiler/test/dotty/tools/scripting/BashScriptsTests.scala +++ b/compiler/test/dotty/tools/scripting/BashScriptsTests.scala @@ -97,11 +97,13 @@ class BashScriptsTests: printf("===> verify SCALA_OPTS='@argsfile' is properly handled by `dist/bin/scala`\n") val envPairs = List(("SCALA_OPTS", s"@$argsfile")) val (validTest, exitCode, stdout, stderr) = bashCommand(scriptFile.absPath, envPairs) + printf("stdout: %s\n", stdout.mkString("\n","\n","")) if validTest then - val expected = s"${workingDirectory.toString}" - val List(line1: String, line2: String) = stdout.take(2) - printf("line1 [%s]\n", line1) - val valid = line2.dropWhile( _ != ' ').trim.startsWith(expected) + val expected = s"${workingDirectory.norm}" + val output = stdout.find( _.trim.startsWith("cwd") ).getOrElse("").dropWhile(_!=' ').trim + printf("output [%s]\n", output) + printf("expected[%s]\n", expected) + val valid = output.startsWith(expected) if valid then printf(s"\n===> success: classpath begins with %s, as reported by [%s]\n", workingDirectory, scriptFile.getName) assert(valid, s"script ${scriptFile.absPath} did not report valid java.class.path first entry") diff --git a/compiler/test/dotty/tools/scripting/ClasspathTests.scala b/compiler/test/dotty/tools/scripting/ClasspathTests.scala index 159605cd967c..82b6affe0c02 100755 --- a/compiler/test/dotty/tools/scripting/ClasspathTests.scala +++ b/compiler/test/dotty/tools/scripting/ClasspathTests.scala @@ -18,12 +18,6 @@ class ClasspathTests: val packBinDir = "dist/target/pack/bin" val packLibDir = "dist/target/pack/lib" - // only interested in classpath test scripts - val testScriptName = "classpathReport.sc" - val testScript = scripts("/scripting").find { _.getName.matches(testScriptName) } match - case None => sys.error(s"test script not found: ${testScriptName}") - case Some(file) => file - def exists(scriptPath: Path): Boolean = Files.exists(scriptPath) def packBinScalaExists:Boolean = exists(Paths.get(s"$packBinDir/scala")) @@ -31,6 +25,12 @@ class ClasspathTests: * verify classpath reported by called script. */ @Test def hashbangClasspathVerifyTest = { + // only interested in classpath test scripts + val testScriptName = "classpathReport.sc" + val testScript = scripts("/scripting").find { _.getName.matches(testScriptName) } match + case None => sys.error(s"test script not found: ${testScriptName}") + case Some(file) => file + val relpath = testScript.toPath.relpath.norm printf("===> hashbangClasspathVerifyTest for script [%s]\n", relpath) printf("bash is [%s]\n", bashExe) @@ -50,13 +50,41 @@ class ClasspathTests: val hashbangClasspathJars = scriptCp.split(psep).map { _.getName }.sorted.distinct val packlibJars = listJars(s"$scriptCwd/$packLibDir").sorted.distinct + printf("%d jar files in dist/target/pack/lib\n", packlibJars.size) + printf("%d test script jars in classpath\n", hashbangClasspathJars.size) + // verify that the classpath set in the hashbang line is effective if hashbangClasspathJars.size != packlibJars.size then - printf("%d test script jars in classpath\n", hashbangClasspathJars.size) - printf("%d jar files in dist/target/pack/lib\n", packlibJars.size) + printf("hashbangClasspathJars: %s\n", hashbangClasspathJars.mkString("\n ", "\n ", "")) assert(hashbangClasspathJars.size == packlibJars.size) } + /* + * verify classpath is unglobbed by MainGenericRunner. + */ + @Test def unglobClasspathVerifyTest = { + val testScriptName = "unglobClasspath.sc" + val testScript = scripts("/scripting").find { _.getName.matches(testScriptName) } match + case None => sys.error(s"test script not found: ${testScriptName}") + case Some(file) => file + + val relpath = testScript.toPath.relpath.norm + printf("===> unglobClasspathVerifyTest for script [%s]\n", relpath) + printf("bash is [%s]\n", bashExe) + + if packBinScalaExists then + val bashCmdline = s"SCALA_OPTS= $relpath" + val cmd = Array(bashExe, "-c", bashCmdline) + + cmd.foreach { printf("[%s]\n", _) } + + // test script reports the classpath it sees + val scriptOutput = exec(cmd:_*) + val scriptCp = findTaggedLine("unglobbed classpath", scriptOutput) + val classpathJars = scriptCp.split(psep).map { _.getName }.sorted.distinct + //classpathJars.foreach { printf("%s\n", _) } + assert(classpathJars.size > 1) + } //////////////// end of tests //////////////// diff --git a/compiler/test/dotty/tools/scripting/ScriptingTests.scala b/compiler/test/dotty/tools/scripting/ScriptingTests.scala index 25fdfe14f5c7..922564d4ff58 100644 --- a/compiler/test/dotty/tools/scripting/ScriptingTests.scala +++ b/compiler/test/dotty/tools/scripting/ScriptingTests.scala @@ -19,7 +19,7 @@ class ScriptingTests: f.getAbsolutePath.replace('\\', '/') // classpath tests managed by scripting.ClasspathTests.scala - def testFiles = scripts("/scripting").filter { ! _.getName.startsWith("classpath") } + def testFiles = scripts("/scripting").filter { ! _.getName.toLowerCase.contains("classpath") } def script2jar(scriptFile: File) = val jarName = s"${scriptFile.getName.dropExtension}.jar"