From c89dc684c191c19d49fa1031e2d3fa9952774d00 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 4 Aug 2016 18:41:56 -0400 Subject: [PATCH] Detect main class in bootstrap command --- .../scala-2.11/coursier/cli/Bootstrap.scala | 20 +-- .../main/scala-2.11/coursier/cli/Helper.scala | 119 ++++++++++++++++- .../main/scala-2.11/coursier/cli/Launch.scala | 124 +----------------- .../scala-2.11/coursier/cli/Options.scala | 4 +- 4 files changed, 137 insertions(+), 130 deletions(-) diff --git a/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala b/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala index 024d87eef..d3e14e981 100644 --- a/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala +++ b/cli/src/main/scala-2.11/coursier/cli/Bootstrap.scala @@ -16,11 +16,6 @@ case class Bootstrap( import scala.collection.JavaConverters._ - if (options.mainClass.isEmpty) { - Console.err.println(s"Error: no main class specified. Specify one with -M or --main") - sys.exit(255) - } - if (!options.standalone && options.downloadDir.isEmpty) { Console.err.println(s"Error: no download dir specified. Specify one with -D or --download-dir") Console.err.println("E.g. -D \"\\$HOME/.app-name/jars\"") @@ -73,7 +68,12 @@ case class Bootstrap( } - val helper = new Helper(options.common, remainingArgs) + val helper = new Helper( + options.common, + remainingArgs, + isolated = options.isolated, + warnBaseLoaderNotFound = false + ) val isolatedDeps = options.isolated.isolatedDeps(options.common.defaultArtifactType) @@ -121,7 +121,9 @@ case class Bootstrap( if (nonHttpUrls.nonEmpty) Console.err.println(s"Warning: non HTTP URLs:\n${nonHttpUrls.mkString("\n")}") - val buffer = new ByteArrayOutputStream() + val mainClass = helper.retainedMainClass + + val buffer = new ByteArrayOutputStream val bootstrapZip = new ZipInputStream(new ByteArrayInputStream(bootstrapJar)) val outputZip = new ZipOutputStream(buffer) @@ -176,8 +178,8 @@ case class Bootstrap( val propsEntry = new ZipEntry("bootstrap.properties") propsEntry.setTime(time) - val properties = new Properties() - properties.setProperty("bootstrap.mainClass", options.mainClass) + val properties = new Properties + properties.setProperty("bootstrap.mainClass", mainClass) if (!options.standalone) properties.setProperty("bootstrap.jarDir", options.downloadDir) diff --git a/cli/src/main/scala-2.11/coursier/cli/Helper.scala b/cli/src/main/scala-2.11/coursier/cli/Helper.scala index cee03e086..922df15ae 100644 --- a/cli/src/main/scala-2.11/coursier/cli/Helper.scala +++ b/cli/src/main/scala-2.11/coursier/cli/Helper.scala @@ -2,7 +2,7 @@ package coursier package cli import java.io.{ OutputStreamWriter, File } -import java.net.URL +import java.net.{ URL, URLClassLoader } import java.util.jar.{ Manifest => JManifest } import java.util.concurrent.Executors @@ -11,6 +11,7 @@ import coursier.util.{Print, Parse} import scala.annotation.tailrec import scala.concurrent.duration.Duration +import scala.util.Try import scalaz.{Failure, Success, \/-, -\/} import scalaz.concurrent.{ Task, Strategy } @@ -75,7 +76,9 @@ class Helper( common: CommonOptions, rawDependencies: Seq[String], printResultStdout: Boolean = false, - ignoreErrors: Boolean = false + ignoreErrors: Boolean = false, + isolated: IsolatedLoaderOptions = IsolatedLoaderOptions(), + warnBaseLoaderNotFound: Boolean = true ) { import common._ import Helper.errPrintln @@ -577,4 +580,116 @@ class Helper( files0 } + + lazy val (parentLoader, filteredFiles) = { + + val contextLoader = Thread.currentThread().getContextClassLoader + + val files0 = fetch(sources = false, javadoc = false) + + val parentLoader0: ClassLoader = + Launch.mainClassLoader(contextLoader) + .flatMap(cl => Option(cl.getParent)) + .getOrElse { + // proguarded -> no risk of conflicts, no absolute need to find a specific ClassLoader + val isProguarded = Try(contextLoader.loadClass("coursier.cli.Launch")).isFailure + if (warnBaseLoaderNotFound && !isProguarded && common.verbosityLevel >= 0) + Console.err.println( + "Warning: cannot find the main ClassLoader that launched coursier.\n" + + "Was coursier launched by its main launcher? " + + "The ClassLoader of the application that is about to be launched will be intertwined " + + "with the one of coursier, which may be a problem if their dependencies conflict." + ) + contextLoader + } + + if (isolated.isolated.isEmpty) + (parentLoader0, files0) + else { + + val isolatedDeps = isolated.isolatedDeps(common.defaultArtifactType) + + val (isolatedLoader, filteredFiles0) = isolated.targets.foldLeft((parentLoader0, files0)) { + case ((parent, files0), target) => + + // FIXME These were already fetched above + val isolatedFiles = fetch( + sources = false, + javadoc = false, + subset = isolatedDeps.getOrElse(target, Seq.empty).toSet + ) + + if (common.verbosityLevel >= 2) { + Console.err.println(s"Isolated loader files:") + for (f <- isolatedFiles.map(_.toString).sorted) + Console.err.println(s" $f") + } + + val isolatedLoader = new IsolatedClassLoader( + isolatedFiles.map(_.toURI.toURL).toArray, + parent, + Array(target) + ) + + val filteredFiles0 = files0.filterNot(isolatedFiles.toSet) + + (isolatedLoader, filteredFiles0) + } + + if (common.verbosityLevel >= 2) { + Console.err.println(s"Remaining files:") + for (f <- filteredFiles0.map(_.toString).sorted) + Console.err.println(s" $f") + } + + (isolatedLoader, filteredFiles0) + } + } + + lazy val loader = new URLClassLoader( + filteredFiles.map(_.toURI.toURL).toArray, + parentLoader + ) + + + lazy val retainedMainClass = { + + val mainClasses = Helper.mainClasses(loader) + + if (common.verbosityLevel >= 2) { + Console.err.println("Found main classes:") + for (((vendor, title), mainClass) <- mainClasses) + Console.err.println(s" $mainClass (vendor: $vendor, title: $title)") + Console.err.println("") + } + + val mainClass = + if (mainClasses.isEmpty) { + Helper.errPrintln("No main class found. Specify one with -M or --main.") + sys.exit(255) + } else if (mainClasses.size == 1) { + val (_, mainClass) = mainClasses.head + mainClass + } else { + // Trying to get the main class of the first artifact + val mainClassOpt = for { + (module, _, _) <- moduleVersionConfigs.headOption + mainClass <- mainClasses.collectFirst { + case ((org, name), mainClass) + if org == module.organization && ( + module.name == name || + module.name.startsWith(name + "_") // Ignore cross version suffix + ) => + mainClass + } + } yield mainClass + + mainClassOpt.getOrElse { + Helper.errPrintln(s"Cannot find default main class. Specify one with -M or --main.") + sys.exit(255) + } + } + + mainClass + } } diff --git a/cli/src/main/scala-2.11/coursier/cli/Launch.scala b/cli/src/main/scala-2.11/coursier/cli/Launch.scala index 8b73ed099..902d8ab81 100644 --- a/cli/src/main/scala-2.11/coursier/cli/Launch.scala +++ b/cli/src/main/scala-2.11/coursier/cli/Launch.scala @@ -76,140 +76,30 @@ case class Launch( val helper = new Helper( options.common, - remainingArgs ++ options.isolated.rawIsolated.map { case (_, dep) => dep } + remainingArgs ++ options.isolated.rawIsolated.map { case (_, dep) => dep }, + isolated = options.isolated ) - - val files0 = helper.fetch(sources = false, javadoc = false) - - val contextLoader = Thread.currentThread().getContextClassLoader - - val parentLoader0: ClassLoader = - Launch.mainClassLoader(contextLoader) - .flatMap(cl => Option(cl.getParent)) - .getOrElse { - // proguarded -> no risk of conflicts, no absolute need to find a specific ClassLoader - val isProguarded = Try(contextLoader.loadClass("coursier.cli.Launch")).isFailure - if (!isProguarded && options.common.verbosityLevel >= 0) - Console.err.println( - "Warning: cannot find the main ClassLoader that launched coursier.\n" + - "Was coursier launched by its main launcher? " + - "The ClassLoader of the application that is about to be launched will be intertwined " + - "with the one of coursier, which may be a problem if their dependencies conflict." - ) - contextLoader - } - - val (parentLoader, filteredFiles) = - if (options.isolated.isolated.isEmpty) - (parentLoader0, files0) - else { - - val isolatedDeps = options.isolated.isolatedDeps(options.common.defaultArtifactType) - - val (isolatedLoader, filteredFiles0) = options.isolated.targets.foldLeft((parentLoader0, files0)) { - case ((parent, files0), target) => - - // FIXME These were already fetched above - val isolatedFiles = helper.fetch( - sources = false, - javadoc = false, - subset = isolatedDeps.getOrElse(target, Seq.empty).toSet - ) - - if (options.common.verbosityLevel >= 2) { - Console.err.println(s"Isolated loader files:") - for (f <- isolatedFiles.map(_.toString).sorted) - Console.err.println(s" $f") - } - - val isolatedLoader = new IsolatedClassLoader( - isolatedFiles.map(_.toURI.toURL).toArray, - parent, - Array(target) - ) - - val filteredFiles0 = files0.filterNot(isolatedFiles.toSet) - - (isolatedLoader, filteredFiles0) - } - - if (options.common.verbosityLevel >= 2) { - Console.err.println(s"Remaining files:") - for (f <- filteredFiles0.map(_.toString).sorted) - Console.err.println(s" $f") - } - - (isolatedLoader, filteredFiles0) - } - - val loader = new URLClassLoader( - filteredFiles.map(_.toURI.toURL).toArray, - parentLoader - ) - - val mainClass0 = - if (options.mainClass.nonEmpty) options.mainClass - else { - val mainClasses = Helper.mainClasses(loader) - - if (options.common.verbosityLevel >= 2) { - Console.err.println("Found main classes:") - for (((vendor, title), mainClass) <- mainClasses) - Console.err.println(s" $mainClass (vendor: $vendor, title: $title)") - Console.err.println("") - } - - val mainClass = - if (mainClasses.isEmpty) { - Helper.errPrintln("No main class found. Specify one with -M or --main.") - sys.exit(255) - } else if (mainClasses.size == 1) { - val (_, mainClass) = mainClasses.head - mainClass - } else { - // Trying to get the main class of the first artifact - val mainClassOpt = for { - (module, _, _) <- helper.moduleVersionConfigs.headOption - mainClass <- mainClasses.collectFirst { - case ((org, name), mainClass) - if org == module.organization && ( - module.name == name || - module.name.startsWith(name + "_") // Ignore cross version suffix - ) => - mainClass - } - } yield mainClass - - mainClassOpt.getOrElse { - Helper.errPrintln(s"Cannot find default main class. Specify one with -M or --main.") - sys.exit(255) - } - } - - mainClass - } - val cls = - try loader.loadClass(mainClass0) + try helper.loader.loadClass(helper.retainedMainClass) catch { case e: ClassNotFoundException => - Helper.errPrintln(s"Error: class $mainClass0 not found") + Helper.errPrintln(s"Error: class ${helper.retainedMainClass} not found") sys.exit(255) } val method = try cls.getMethod("main", classOf[Array[String]]) catch { case e: NoSuchMethodException => - Helper.errPrintln(s"Error: method main not found in $mainClass0") + Helper.errPrintln(s"Error: method main not found in ${helper.retainedMainClass}") sys.exit(255) } method.setAccessible(true) if (options.common.verbosityLevel >= 2) - Helper.errPrintln(s"Launching $mainClass0 ${userArgs.mkString(" ")}") + Helper.errPrintln(s"Launching ${helper.retainedMainClass} ${userArgs.mkString(" ")}") else if (options.common.verbosityLevel == 1) Helper.errPrintln(s"Launching") - Thread.currentThread().setContextClassLoader(loader) + Thread.currentThread().setContextClassLoader(helper.loader) try method.invoke(null, userArgs.toArray) catch { case e: java.lang.reflect.InvocationTargetException => diff --git a/cli/src/main/scala-2.11/coursier/cli/Options.scala b/cli/src/main/scala-2.11/coursier/cli/Options.scala index 905368710..d084f3e8c 100644 --- a/cli/src/main/scala-2.11/coursier/cli/Options.scala +++ b/cli/src/main/scala-2.11/coursier/cli/Options.scala @@ -98,10 +98,10 @@ case class CacheOptions( case class IsolatedLoaderOptions( @Value("target:dependency") @Short("I") - isolated: List[String], + isolated: List[String] = Nil, @Help("Comma-separated isolation targets") @Short("i") - isolateTarget: List[String] + isolateTarget: List[String] = Nil ) { def anyIsolatedDep = isolateTarget.nonEmpty || isolated.nonEmpty