Skip to content

fix for #10761 ; expand wildcard classpath entries #11633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 21, 2021
Merged

fix for #10761 ; expand wildcard classpath entries #11633

merged 15 commits into from
Apr 21, 2021

Conversation

philwalk
Copy link
Contributor

@philwalk philwalk commented Mar 6, 2021

This fixes #10761

Wildcard classpath entries are expanded, which solves 2 problems:

  1. The -save option requires expanded classpath wildcard entries in jar manifest
  2. The runtime classpath requires expanding -classpath wildcard entries

The solution is not os-dependent, the jar manifest Class-Path property requires expanded wildcards.

The callback classpath:String parameter in compiler/src/dotty/tools/scripting/ScriptingDriver.scala was changed to classpathEntries:Seq[String] so the expanded classpath could also be seen by compiler/src/dotty/tools/scripting/Main.scala and could to be written to the jar manifest.

Two similar use-cases fixed by the PR each required a different fix:

A classpath consisting of a single wildcard entry, specified on the command line (fixed by appending $PSEP to the classpath):

$ scala -classpath 'lib/*' script.sc

A classpath consisting of a single wildcard entry, specified in a hashbang (fixed by accepting '-classpath' and 'lib/*' passed together as a single argument to dist/bin/scala). An example of this use-case:

#!/opt/scala3/bin/scala -classpath 'lib/*'
def main(args: Array[String]): Unit =
  val psep = sys.props("path.separator")
  for entry <- sys.props("java.class.path").split(psep) do
    printf("%s\n",entry)

@michelou
Copy link
Contributor

michelou commented Mar 8, 2021

I'm wondering if the Scripting execution engine is the only place in the Scala 3 compiler that has to take care of wildcard classpaths. Any thoughts about it ?!

@philwalk
Copy link
Contributor Author

philwalk commented Mar 8, 2021

I'm not aware of where else in the Scala 3 compiler there might also be a need to expand classpath. I wasn't able to produce problems except those that this PR addresses.

@smarter
Copy link
Member

smarter commented Mar 8, 2021

I'm not aware of where else in the Scala 3 compiler there might also be a need to expand classpath

My comment in #10761 (comment) points you to the code in the compiler which currently handles expanding wildcard, reusing that code would be good.

@philwalk
Copy link
Contributor Author

philwalk commented Mar 9, 2021

@smarter - thanks, I updated the PR.

compiler/src/dotty/tools/scripting/ScriptingDriver.scala now uses the expandPath() method in compiler/src/dotty/tools/io/ClassPath.scala.

expandPath() was extended to include classpath entries with forward slash, regardless of OS.

Test class added to verify:

  • multiple wildcard entries in the classpath
  • bogus classpath entries don't cause a crash

@michelou
Copy link
Contributor

michelou commented Mar 9, 2021

@philwalk I'm uncomfortable with your addition on line 135 in file io/ClassPath.scala. Can you explain how testing both wildSuffix (equals File.separator + "*") and "/*" does make sense in the context of this PR ?

@philwalk
Copy link
Contributor Author

philwalk commented Mar 9, 2021

@michelou
The existing wildSuffix test assumes that Windows classpaths use only backslash, but the jdk supports forward slash as well.
The classpath reported in #10761 -classpath 'c:/opt/uejlib2.13/*', has forward slash.

In Windows, the additional test can prevent the wildcard expansion from failing.

An alternative might be something like the following:

  /** Expand single path entry */
  private def expandS(pattern: String): List[String] = {
    val wildSuffix = "/*"

    /* Get all subdirectories, jars, zips out of a directory. */
    def lsDir(dir: Directory, filt: String => Boolean = _ => true) =
      dir.list.filter(x => filt(x.name) && (x.isDirectory || isJarOrZip(x))).map(_.path).toList

    if (pattern == "*") lsDir(Directory("."))
    else if (pattern.replace('\\','/').endsWith(wildSuffix)) lsDir(Directory(pattern dropRight 2))
    else if (pattern.contains('*')) {
      try {
        val regexp = ("^" + pattern.replace("""\*""", """.*""") + "$").r
        lsDir(Directory(pattern).parent, regexp.findFirstIn(_).isDefined)
      }
      catch { case _: PatternSyntaxException => List(pattern) }
    }
    else List(pattern)
  }

@michelou
Copy link
Contributor

michelou commented Mar 9, 2021

@michelou
The existing wildSuffix test assumes that Windows classpaths use only backslash, but the jdk supports forward slash as well.
The classpath reported in #10761 -classpath 'c:/opt/uejlib2.13/*', has forward slash.

In Windows, the additional test can prevent the wildcard expansion from failing.

An alternative might be something like the following:

  /** Expand single path entry */
  private def expandS(pattern: String): List[String] = {
    val wildSuffix = "/*"

    /* Get all subdirectories, jars, zips out of a directory. */
    def lsDir(dir: Directory, filt: String => Boolean = _ => true) =
      dir.list.filter(x => filt(x.name) && (x.isDirectory || isJarOrZip(x))).map(_.path).toList

    if (pattern == "*") lsDir(Directory("."))
    else if (pattern.replace('\\','/').endsWith(wildSuffix)) lsDir(Directory(pattern dropRight 2))
    else if (pattern.contains('*')) {
      try {
        val regexp = ("^" + pattern.replace("""\*""", """.*""") + "$").r
        lsDir(Directory(pattern).parent, regexp.findFirstIn(_).isDefined)
      }
      catch { case _: PatternSyntaxException => List(pattern) }
    }
    else List(pattern)
  }

So I would add the following comment befote the modified line :

// On Windows the JDK supports forward slash as well.

@philwalk
Copy link
Contributor Author

philwalk commented Mar 9, 2021

@michelou - comment was added to line 135 of compiler/src/dotty/tools/io/ClassPath.scala as per your suggestion

@philwalk
Copy link
Contributor Author

After the change to leverage ClassPath#expandPath, a stack dump similar to the original error reported in #10761 has recurred in the -classpath lib/* use case.

I'm investigating,

@philwalk
Copy link
Contributor Author

Added code to expand wildcards in compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala.

I will improve the coverage in the classpath test compiler/test/dotty/tools/io/ClasspathTest.scala

@philwalk
Copy link
Contributor Author

It turns out that Windows jdk 11 (and maybe earlier versions too?) expand all command line arguments that look like wildcard classpath entries.

As a result, -classpath args on the command line are expanded (Windows only), even if escaped and quoted, etc..

If a classpath is specified in the hashbang header, it doesn't get expanded. The most recent commit covers this use case.

@michelou
Copy link
Contributor

@philwalk In file ClasspathTest.scala duplicate import on line 22.

@philwalk
Copy link
Contributor Author

@michelou

@philwalk In file ClasspathTest.scala duplicate import on line 22.

I deleted the extra import, and will commit after I add some more tests (hopefully soon).

@philwalk
Copy link
Contributor Author

philwalk commented Mar 16, 2021

While testing this PR, I discovered an unrelated problem with dist/bin/scalac
Here's the simple fix:

         -repl) PROG_NAME="$ReplMain" && shift ;;
-      -script) PROG_NAME="$ScriptingMain" && target_script="$2" && in_scripting_args=true && shift && shift ;;
+      -script) PROG_NAME="$ScriptingMain" && target_script="$2" && in_scripting_args=true && shift && shift
+               while [[ $# -gt 0 ]]; do addScripting "$1" && shift ; done ;;
      -compile) PROG_NAME="$CompilerMain" && shift ;;

The bug manifests itself if the target_script also has -script or -repl arguments, etc.

What's the recommended process for submitting a fix?
Should I file a bug report first?
Should I add the fix to this PR, or create a new PR?

@smarter
Copy link
Member

smarter commented Mar 16, 2021

Since this PR is complicated enough as it is, I suggest making another PR for that unrelated change.

@liufengyun
Copy link
Contributor

@michelou Is this one ready to go?

@philwalk
Copy link
Contributor Author

@michelou I'm working on tests to thoroughly verify this PR, but they aren't ready yet.
I have done lots of manual testing, so I believe it's safe to merge.

@philwalk
Copy link
Contributor Author

  • added tests to verify windows wildcard classpath in [dist/bin/scala
  • added ability for tests to verify [dist/bin/scalac] generated java command line

@philwalk
Copy link
Contributor Author

philwalk commented Mar 17, 2021

@michelou -

@philwalk Your addition on lines 122-126 in file bin/scalac is temporary right ?!

It can be temporary if there are objections to it. We possibly can generate an edited copy of scalac and call that at test time.

The reason I added it was to be able to test scala, scalac and scaladoc scripts. I originally tried to diagnose the same thing from inside of a running script, but there are challenges and I haven't got that approach to work yet.

In the test file ClasspathTest.scala on line 38:

      val cmd = Array(bashExe,"-c",s"SCALAC_ECHO_TEST=1 dist/target/pack/bin/scala -classpath 'lib/* $relpath")

To see what happens, you can remove the SCALAC_ECHO_TEST from the test:

      val cmd = Array(bashExe,"-c",s"dist/target/pack/bin/scala -classpath 'lib/*' $relpath")

It fails in Windows with a complaint about mismatched class file versions. (unable to duplicate)

@philwalk
Copy link
Contributor Author

When I run sbt tests from a Windows command shell, the new classpath test fails, so I'm working on how to handle it.

@michelou
Copy link
Contributor

@philwalk Note about style : please add a space after commas, e.g.
val cmd = Array(bashExe, "-c", bashCmdline)
instead of
val cmd = Array(bashExe,"-c",bashCmdline)

@philwalk
Copy link
Contributor Author

philwalk commented Mar 18, 2021

Classpath tests now cover the two most difficult problematic use cases:
A classpath consisting of a single wildcard entry, specified on the command line:

$ scala -classpath 'lib/*' script.sc

A classpath consisting of a single wildcard entry, specified in a hashbang:

#!/opt/scala3/bin/scala -classpath 'lib/*'
def main(args: Array[String]): Unit =
  val psep = sys.props("path.separator")
  for entry <- sys.props("java.class.path").split(psep) do
    printf("%s\n",entry)

One version of the single wildcard classpath use cases (in Windows) results in all the jar files in the the wildcard directory being expanded and passed as arguments to the compiler. The compiler seems to treat them as if they're source files and produces thousands of error messages.

Unfortunately, I checked in a bug in [dist/bin/scala], I'll push the fix after the current checks complete.
BTW, is there a way to cancel the current checks to avoid wasting resources?

@philwalk
Copy link
Contributor Author

@michelou

@philwalk Note about style : please add a space after commas, e.g.
val cmd = Array(bashExe, "-c", bashCmdline)
instead of
val cmd = Array(bashExe,"-c",bashCmdline)

I'll go through and check for style errors.

@philwalk
Copy link
Contributor Author

I'm ready to push a bug fix and corrected style errors, should I wait until the current checks are completed?
It would be nice if we could cancel them.

@philwalk
Copy link
Contributor Author

The comment on the most recent commit should have been:
'added missing shift in new -classpath* case in dist/bin/scala; fixed various style errors'

@michelou
Copy link
Contributor

The comment on the most recent commit should have been:
'added missing shift in new -classpath* case in dist/bin/scala; fixed various style errors'

Strange. Job 0dc35d8 seems to be still running ?!

@philwalk
Copy link
Contributor Author

The comment on the most recent commit should have been:
'added missing shift in new -classpath* case in dist/bin/scala; fixed various style errors'

Strange. Job 0dc35d8 seems to be still running ?!

@michelou It seems to be finished now (4 hours after your comment).

@philwalk philwalk closed this Mar 26, 2021
@philwalk
Copy link
Contributor Author

close and re-open to trigger a rebuild ... there does seem to be something odd about the community build status reporting.

@philwalk philwalk reopened this Mar 26, 2021
@philwalk
Copy link
Contributor Author

philwalk commented Mar 28, 2021

@philwalk Your addition on lines 122-126 in file bin/scalac is temporary right ?!

@michelou I'm ready to push an updated version of compiler/test/dotty/tools/scripting/ClasspathTests.scala that eliminates the SCALAC_ECHO_TEST change to dist/bin/scalac, based on testable modified copies of [dist/bin/scala] and [dist/bin/scalac].

Are you aware of any other changes that are needed for this PR?

@michelou
Copy link
Contributor

@philwalk Your file dist/bin/scalac differs from current head (e.g. lines 92-93). Please check it again.

@philwalk
Copy link
Contributor Author

@philwalk Your file dist/bin/scalac differs from current head (e.g. lines 92-93). Please check it again.

@michelou - I will push my changes now, the link above is to the original proposal which has the objectionable lines.

Copy link
Contributor

@liufengyun liufengyun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks @philwalk 🎉

@liufengyun
Copy link
Contributor

@michelou I'm merging this now. Feel free to open a new issue if you spot some problems. Thank you.

@liufengyun liufengyun merged commit f7b5594 into scala:master Apr 21, 2021
@philwalk philwalk deleted the scripting-classpath-wildcard-fix-10761 branch April 21, 2021 15:12
@Kordyjan Kordyjan added this to the 3.0.1 milestone Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

broken support for use of wildcards in the compiler classpath on Windows
5 participants