mirror of https://github.com/sbt/sbt.git
Add in the ability to launch non xsbti.AppMain classes.
* Deprecate old mainClass method on appProvider * Create entryPoint method which represnets class used to launch instead. * Create PlainApplication to wrap static `main` methods. Can return either Int, Exit or Unit. * Detect supported 'plain' classes via reflection. * Add new unit tests appropriate to the feature.
This commit is contained in:
parent
53b4edd79f
commit
b0f6f94839
|
|
@ -8,13 +8,27 @@ public interface AppProvider
|
|||
public ScalaProvider scalaProvider();
|
||||
/** The ID of the application that will be created by 'newMain' or 'mainClass'.*/
|
||||
public ApplicationID id();
|
||||
|
||||
public ClassLoader loader();
|
||||
/** Loads the class for the entry point for the application given by 'id'. This method will return the same class
|
||||
* every invocation. That is, the ClassLoader is not recreated each call.*/
|
||||
/** The classloader used to load this application. */
|
||||
public ClassLoader loader();
|
||||
/** Loads the class for the entry point for the application given by 'id'.
|
||||
* This method will return the same class every invocation.
|
||||
* That is, the ClassLoader is not recreated each call.
|
||||
* @deprecated("use entryPoint instead")
|
||||
*
|
||||
* Note: This will throw an exception if the launched application does not extend AppMain.
|
||||
*/
|
||||
@Deprecated
|
||||
public Class<? extends AppMain> mainClass();
|
||||
/** Loads the class for the entry point for the application given by 'id'.
|
||||
* This method will return the same class every invocation.
|
||||
* That is, the ClassLoader is not recreated each call.
|
||||
*/
|
||||
public Class<?> entryPoint();
|
||||
/** Creates a new instance of the entry point of the application given by 'id'.
|
||||
* It is guaranteed that newMain().getClass() == mainClass()*/
|
||||
* It is NOT guaranteed that newMain().getClass() == mainClass().
|
||||
* The sbt launcher can wrap generic static main methods. In this case, there will be a wrapper class,
|
||||
* and you must use the `entryPoint` method.
|
||||
*/
|
||||
public AppMain newMain();
|
||||
|
||||
/** The classpath from which the main class is loaded, excluding Scala jars.*/
|
||||
|
|
|
|||
|
|
@ -238,12 +238,20 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i
|
|||
val id = appID
|
||||
def mainClasspath = app.fullClasspath
|
||||
lazy val loader = app.createLoader(scalaProvider.loader)
|
||||
lazy val mainClass: Class[T] forSome { type T <: xsbti.AppMain } =
|
||||
lazy val entryPoint: Class[T] forSome { type T } =
|
||||
{
|
||||
val c = Class.forName(id.mainClass, true, loader)
|
||||
c.asSubclass(classOf[xsbti.AppMain])
|
||||
if(classOf[xsbti.AppMain].isAssignableFrom(c)) c
|
||||
else if(PlainApplication.isPlainApplication(c)) c
|
||||
else sys.error(s"Class: ${c} is not an instance of xsbti.AppMain nor does it have one of these static methods:\n"+
|
||||
" * void main(String[] args)\n * int main(String[] args)\n * xsbti.Exit main(String[] args)")
|
||||
}
|
||||
// Deprecated API. Remove when we can.
|
||||
def mainClass: Class[T] forSome { type T <: xsbti.AppMain } = entryPoint.asSubclass(classOf[xsbti.AppMain])
|
||||
def newMain(): xsbti.AppMain = {
|
||||
if(PlainApplication.isPlainApplication(entryPoint)) PlainApplication(entryPoint)
|
||||
else mainClass.newInstance
|
||||
}
|
||||
def newMain(): xsbti.AppMain = mainClass.newInstance
|
||||
|
||||
lazy val components = componentProvider(appHome)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package xsbt
|
||||
package boot
|
||||
|
||||
/** A wrapper around 'raw' static methods to meet the sbt application interface. */
|
||||
class PlainApplication private (mainMethod: java.lang.reflect.Method) extends xsbti.AppMain {
|
||||
override def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = {
|
||||
// TODO - Figure out if the main method returns an Int...
|
||||
val IntClass = classOf[Int]
|
||||
val ExitClass = classOf[xsbti.Exit]
|
||||
// It seems we may need to wrap exceptions here...
|
||||
try mainMethod.getReturnType match {
|
||||
case ExitClass =>
|
||||
mainMethod.invoke(null, configuration.arguments).asInstanceOf[xsbti.Exit]
|
||||
case IntClass =>
|
||||
PlainApplication.Exit(mainMethod.invoke(null, configuration.arguments).asInstanceOf[Int])
|
||||
case _ =>
|
||||
// Here we still invoke, but return 0 if sucessful (no exceptions).
|
||||
mainMethod.invoke(null, configuration.arguments)
|
||||
PlainApplication.Exit(0)
|
||||
} catch {
|
||||
// This is only thrown if the underlying reflective call throws.
|
||||
// Let's expose the underlying error.
|
||||
case e: java.lang.reflect.InvocationTargetException if e.getCause != null =>
|
||||
throw e.getCause
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
/** An object that lets us detect compatible "plain" applications and launch them reflectively. */
|
||||
object PlainApplication {
|
||||
def isPlainApplication(clazz: Class[_]): Boolean = findMainMethod(clazz).isDefined
|
||||
def apply(clazz: Class[_]): xsbti.AppMain =
|
||||
findMainMethod(clazz) match {
|
||||
case Some(method) => new PlainApplication(method)
|
||||
case None => sys.error("Class: " + clazz + " does not have a main method!")
|
||||
}
|
||||
private def findMainMethod(clazz: Class[_]): Option[java.lang.reflect.Method] =
|
||||
try {
|
||||
val method =
|
||||
clazz.getMethod("main", classOf[Array[String]])
|
||||
if(java.lang.reflect.Modifier.isStatic(method.getModifiers)) Some(method)
|
||||
else None
|
||||
} catch {
|
||||
case n: NoSuchMethodException => None
|
||||
}
|
||||
|
||||
case class Exit(code: Int) extends xsbti.Exit
|
||||
}
|
||||
|
|
@ -23,12 +23,21 @@ object ScalaProviderTest extends Specification
|
|||
checkLoad(List("test"), "xsbt.boot.test.ArgumentTest").asInstanceOf[Exit].code must equalTo(0)
|
||||
checkLoad(List(), "xsbt.boot.test.ArgumentTest") must throwA[RuntimeException]
|
||||
}
|
||||
"Successfully load an plain application from local repository and run it with correct arguments" in {
|
||||
checkLoad(List("test"), "xsbt.boot.test.PlainArgumentTest").asInstanceOf[Exit].code must equalTo(0)
|
||||
checkLoad(List(), "xsbt.boot.test.PlainArgumentTest") must throwA[RuntimeException]
|
||||
}
|
||||
"Successfully load an plain application with int return from local repository and run it with correct arguments" in {
|
||||
checkLoad(List("test"), "xsbt.boot.test.PlainArgumentTestWithReturn").asInstanceOf[Exit].code must equalTo(0)
|
||||
checkLoad(List(), "xsbt.boot.test.PlainArgumentTestWithReturn").asInstanceOf[Exit].code must equalTo(1)
|
||||
}
|
||||
"Successfully load an application from local repository and run it with correct sbt version" in {
|
||||
checkLoad(List(AppVersion), "xsbt.boot.test.AppVersionTest").asInstanceOf[Exit].code must equalTo(0)
|
||||
}
|
||||
"Add extra resources to the classpath" in {
|
||||
checkLoad(testResources, "xsbt.boot.test.ExtraTest", createExtra).asInstanceOf[Exit].code must equalTo(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def checkLoad(arguments: List[String], mainClassName: String): MainResult =
|
||||
|
|
|
|||
|
|
@ -32,4 +32,14 @@ class ExtraTest extends xsbti.AppMain
|
|||
}
|
||||
new Exit(0)
|
||||
}
|
||||
}
|
||||
object PlainArgumentTestWithReturn {
|
||||
def main(args: Array[String]): Int =
|
||||
if(args.length == 0) 1
|
||||
else 0
|
||||
}
|
||||
object PlainArgumentTest {
|
||||
def main(args: Array[String]): Unit =
|
||||
if(args.length == 0) throw new MainException("Arguments were empty")
|
||||
else ()
|
||||
}
|
||||
Loading…
Reference in New Issue