Detect main class in bootstrap command

This commit is contained in:
Alexandre Archambault 2016-08-04 18:41:56 -04:00
parent 419ff74a98
commit c89dc684c1
No known key found for this signature in database
GPG Key ID: 14640A6839C263A9
4 changed files with 137 additions and 130 deletions

View File

@ -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)

View File

@ -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
}
}

View File

@ -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 =>

View File

@ -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