Repository: scalameta/scalameta (scala)

Issue:
metac is fragile on JDK 11 (URLClassLoader/app class loader)
One of the changes in JDK 11 is that the application classloader isn't a `URLClassLoader`, see https://blog.codefx.org/java/java-11-migration-guide/#Casting-To-URL-Class-Loader.

While looking at https://scalameta.org/docs/semanticdb/guide.html#installation, I switch the instructions to use Coursier's sexy, new `cs` launchers, and found it to blow up because I was using JDK 11:

```bash
$ cs launch org.scalameta:metac_2.13.1:4.3.0 -- -cp $(cs fetch -p org.scala-lang:scala-library:2.13.1) Test.scala
Exception in thread "main" java.lang.RuntimeException: unsupported classloader: jdk.internal.loader.ClassLoaders$AppClassLoader@6ff3c5b5
	at scala.sys.package$.error(package.scala:27)
	at scala.meta.internal.metac.Main.process(Main.scala:23)
	at scala.meta.cli.Metac$.process(Metac.scala:36)
	at scala.meta.cli.Metac$.process(Metac.scala:27)
	at scala.meta.cli.Metac$.main(Metac.scala:10)
	at scala.meta.cli.Metac.main(Metac.scala)
```

I opened the issue in Coursier (https://github.com/coursier/coursier/issues/1572) and found out it's also possible to blow it up with the old launcher, by making it spawn a new JVM:

```bash
$ coursier launch --fork org.scalameta:metac_2.13.1:4.3.0 -- -cp $(coursier fetch -p org.scala-lang:scala-library:2.13.1) Test.scala
Exception in thread "main" java.lang.RuntimeException: unsupported classloader: jdk.internal.loader.ClassLoaders$AppClassLoader@6ff3c5b5
	at scala.sys.package$.error(package.scala:27)
	at scala.meta.internal.metac.Main.process(Main.scala:23)
	at scala.meta.cli.Metac$.process(Metac.scala:36)
	at scala.meta.cli.Metac$.process(Metac.scala:27)
	at scala.meta.cli.Metac$.main(Metac.scala:10)
	at scala.meta.cli.Metac.main(Metac.scala)
```

(There's also a few more references to URLClassLoader in the repo: https://github.com/scalameta/scalameta/search?q=URLClassLoader.)

Pull request description:
In Java 11, URLClassLoader is no longer used, and the only documented simple solution is to use `java.class.path` system property. Possibly fixes #1985.

Patch:
diff --git a/semanticdb/metac/src/main/scala/scala/meta/internal/metac/Main.scala b/semanticdb/metac/src/main/scala/scala/meta/internal/metac/Main.scala
index d019360ddb..159670df23 100644
--- a/semanticdb/metac/src/main/scala/scala/meta/internal/metac/Main.scala
+++ b/semanticdb/metac/src/main/scala/scala/meta/internal/metac/Main.scala
@@ -1,11 +1,11 @@
 package scala.meta.internal.metac
 
 import scala.meta.cli._
+import scala.meta.internal.classpath.ClasspathUtils
 import scala.meta.internal.semanticdb.scalac._
 import scala.meta.metac._
 
 import java.io._
-import java.net._
 import java.nio.channels._
 import java.nio.file._
 
@@ -21,8 +21,7 @@ class Main(settings: Settings, reporter: Reporter) {
     manifestStream.close()
     val pluginClasspath = classOf[SemanticdbPlugin].getClassLoader match {
       case null => manifestDir.toString
-      case cl: URLClassLoader => cl.getURLs.map(_.getFile).mkString(File.pathSeparator)
-      case cl => sys.error(s"unsupported classloader: $cl")
+      case cl => ClasspathUtils.getClassPathEntries(cl).mkString(File.pathSeparator)
     }
     val enablePluginArgs = List("-Xplugin:" + pluginClasspath, "-Xplugin-require:semanticdb")
     val enableRangeposArgs = List("-Yrangepos")
diff --git a/semanticdb/metacp/src/main/scala/scala/meta/internal/metacp/Main.scala b/semanticdb/metacp/src/main/scala/scala/meta/internal/metacp/Main.scala
index 366b879169..162203d394 100644
--- a/semanticdb/metacp/src/main/scala/scala/meta/internal/metacp/Main.scala
+++ b/semanticdb/metacp/src/main/scala/scala/meta/internal/metacp/Main.scala
@@ -9,7 +9,6 @@ import scala.meta.io._
 import scala.meta.metacp._
 
 import java.io.BufferedOutputStream
-import java.net.URLClassLoader
 import java.nio.charset.StandardCharsets
 import java.nio.file._
 import java.nio.file.attribute.BasicFileAttributes
@@ -198,17 +197,9 @@ class Main(settings: Settings, reporter: Reporter) {
 
   private def detectJavacp: Classpath =
     if (settings.usejavacp) {
-      val scalaLibrary = this.getClass.getClassLoader match {
-        case loader: URLClassLoader => loader.getURLs.collectFirst {
-            case url if url.toString.contains("scala-library") => AbsolutePath(Paths.get(url.toURI))
-          }.getOrElse {
-            throw new IllegalStateException("Unable to detect scala-library via --usejavacp")
-          }
-        case unexpected => throw new IllegalStateException(
-            s"Expected this.getClass.getClassLoader to be URLClassLoader. " +
-              s"Obtained $unexpected"
-          )
-      }
-      Classpath(scalaLibrary)
+      val scalaLibraryPath = ClasspathUtils.getClassPathEntries(this.getClass.getClassLoader)
+        .collectFirst { case x if x.toString.contains("scala-library") => x }
+        .getOrElse(throw new IllegalStateException("Unable to detect scala-library via --usejavacp"))
+      Classpath(scalaLibraryPath)
     } else Classpath(Nil)
 }
diff --git a/semanticdb/scalac/library/src/main/scala/scala/meta/interactive/InteractiveSemanticdb.scala b/semanticdb/scalac/library/src/main/scala/scala/meta/interactive/InteractiveSemanticdb.scala
index 0fd3c11ca9..f349f8ff31 100644
--- a/semanticdb/scalac/library/src/main/scala/scala/meta/interactive/InteractiveSemanticdb.scala
+++ b/semanticdb/scalac/library/src/main/scala/scala/meta/interactive/InteractiveSemanticdb.scala
@@ -1,12 +1,10 @@
 package scala.meta.interactive
 
+import scala.meta.internal.classpath.ClasspathUtils
 import scala.meta.internal.semanticdb.scalac._
 import scala.meta.internal.{semanticdb => s}
-import scala.meta.io.AbsolutePath
 
 import java.io.File
-import java.net.URLClassLoader
-import java.nio.file.Files
 
 import scala.reflect.io.VirtualDirectory
 import scala.tools.nsc.Settings
@@ -110,10 +108,8 @@ object InteractiveSemanticdb extends VersionCompilerOps {
     richUnit
   }
 
-  private def thisClasspath: String = this.getClass.getClassLoader match {
-    case url: URLClassLoader => url.getURLs.map(_.toURI.getPath).mkString(File.pathSeparator)
-    case els => throw new IllegalStateException(s"Expected URLClassloader, got $els")
-  }
+  private def thisClasspath: String = ClasspathUtils
+    .getClassPathEntries(this.getClass.getClassLoader).mkString(File.pathSeparator)
 
   private def ask[A](f: Response[A] => Unit): Response[A] = {
     val r = new Response[A]
diff --git a/semanticdb/scalac/library/src/main/scala/scala/meta/internal/classpath/ClasspathUtils.scala b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/classpath/ClasspathUtils.scala
new file mode 100644
index 0000000000..2874d910f4
--- /dev/null
+++ b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/classpath/ClasspathUtils.scala
@@ -0,0 +1,21 @@
+package scala.meta.internal.classpath
+
+import java.io.File
+import java.net.URLClassLoader
+import java.nio.file.Path
+import java.nio.file.Paths
+
+object ClasspathUtils {
+
+  def getDefaultClassPathEntries: Seq[Path] = System.getProperty("java.class.path")
+    .split(File.pathSeparator).map(x => Paths.get(x))
+
+  def getClassPathEntriesOpt(cl: ClassLoader): Option[Seq[Path]] = cl match {
+    case cl: URLClassLoader => Some(cl.getURLs.map(x => Paths.get(x.toURI)))
+    case _ => None
+  }
+
+  def getClassPathEntries(cl: ClassLoader): Seq[Path] = getClassPathEntriesOpt(cl)
+    .getOrElse(getDefaultClassPathEntries)
+
+}


Test patch:
diff --git a/tests-semanticdb/src/test/scala/scala/meta/tests/semanticdb/MetacScalaLibrary.scala b/tests-semanticdb/src/test/scala/scala/meta/tests/semanticdb/MetacScalaLibrary.scala
index b044a7bc90..aa29131b1f 100644
--- a/tests-semanticdb/src/test/scala/scala/meta/tests/semanticdb/MetacScalaLibrary.scala
+++ b/tests-semanticdb/src/test/scala/scala/meta/tests/semanticdb/MetacScalaLibrary.scala
@@ -1,13 +1,13 @@
 package scala.meta.tests.semanticdb
 
 import scala.meta.cli._
+import scala.meta.internal.classpath.ClasspathUtils
 import scala.meta.internal.io.FileIO
 import scala.meta.io.AbsolutePath
 import scala.meta.metac._
 import scala.meta.tests.BuildInfo
 
 import java.io.File
-import java.net.URLClassLoader
 import java.nio.file.Files
 import java.nio.file.Paths
 
@@ -41,7 +41,7 @@ object MetacScalaLibrary {
       Files.isDirectory(library),
       s"$library is not a directory! Run `sbt download-scala-library`"
     )
-    val classpath = this.getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs.map(_.getPath)
+    val classpath = ClasspathUtils.getClassPathEntries(this.getClass.getClassLoader).map(_.toString)
       .filter(_.contains("scala-library")).mkString(File.pathSeparator)
     val files = FileIO.listAllFilesRecursively(AbsolutePath(library)).map(_.toString)
       .filter(_.endsWith("scala"))
diff --git a/tests/jvm/src/test/scala-2.13/scala/meta/tests/testkit/ScalacParser.scala b/tests/jvm/src/test/scala-2.13/scala/meta/tests/testkit/ScalacParser.scala
index 4a08567dd6..f09bf10977 100644
--- a/tests/jvm/src/test/scala-2.13/scala/meta/tests/testkit/ScalacParser.scala
+++ b/tests/jvm/src/test/scala-2.13/scala/meta/tests/testkit/ScalacParser.scala
@@ -1,6 +1,7 @@
 package scala.meta.tests.testkit
 
 import java.io.File
+import java.net.URLClassLoader
 
 import scala.collection.mutable
 import scala.reflect.internal.util.CodeAction
@@ -15,11 +16,13 @@ object ScalacParser {
   var current: ClassLoader = Thread.currentThread().getContextClassLoader
   val files: mutable.Buffer[File] = collection.mutable.Buffer.empty[java.io.File]
   val settings = new Settings()
-  files.appendAll(System.getProperty("sun.boot.class.path").split(":").map(new java.io.File(_)))
+  Seq("sun.boot.class.path", "java.class.path").foreach { prop =>
+    System.getProperty(prop).split(File.pathSeparator)
+      .foreach(entry => files.append(new File(entry)))
+  }
   while (current != null) {
     current match {
-      case t: java.net.URLClassLoader => files
-          .appendAll(t.getURLs.map(u => new java.io.File(u.toURI)))
+      case t: URLClassLoader => t.getURLs.foreach(u => files.append(new File(u.toURI)))
       case _ =>
     }
     current = current.getParent