mirror of https://github.com/sbt/sbt.git
Safer template resolver
Fixes #2761 With sbt 0.13.13-RC1 rediscovered that the dependency pulled in from Giter8 was affecting the plugins. To avoid this, this change splits up the template resolver implementation to another module called sbt-giter8-resolver, and it will be downloaded using Ivy into `~/.sbt/0.13/templates/`, and then launched reflectively using Java as the interface.
This commit is contained in:
parent
729119a15a
commit
1b79cb85b6
|
|
@ -192,7 +192,7 @@ lazy val commandProj = (project in file("main-command")).
|
|||
settings(
|
||||
testedBaseSettings,
|
||||
name := "Command",
|
||||
libraryDependencies ++= Seq(launcherInterface, sjsonNewScalaJson, templateResolverApi, giter8),
|
||||
libraryDependencies ++= Seq(launcherInterface, sjsonNewScalaJson, templateResolverApi),
|
||||
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
|
||||
contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats
|
||||
).
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import sbt.io.IO
|
|||
import scala.util.control.NonFatal
|
||||
|
||||
object BasicCommands {
|
||||
lazy val allBasicCommands = Seq(nop, ignore, help, completionsCommand, templateCommand, multi, ifLast, append, setOnFailure, clearOnFailure,
|
||||
lazy val allBasicCommands = Seq(nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure,
|
||||
stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, shell, server, client, read, alias) ++ compatCommands
|
||||
|
||||
def nop = Command.custom(s => success(() => s))
|
||||
|
|
@ -82,9 +82,6 @@ object BasicCommands {
|
|||
state
|
||||
}
|
||||
|
||||
<<<<<<< HEAD:main-command/src/main/scala/sbt/BasicCommands.scala
|
||||
def multiParser(s: State): Parser[List[String]] =
|
||||
=======
|
||||
def templateCommand = Command.make(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser)
|
||||
def templateCommandParser(state: State) =
|
||||
{
|
||||
|
|
@ -110,8 +107,7 @@ object BasicCommands {
|
|||
})
|
||||
}
|
||||
|
||||
def multiParser(s: State): Parser[Seq[String]] =
|
||||
>>>>>>> 954e744... Adds templateResolvers and `new` command:main/command/src/main/scala/sbt/BasicCommands.scala
|
||||
def multiParser(s: State): Parser[List[String]] =
|
||||
{
|
||||
val nonSemi = token(charClass(_ != ';').+, hide = const(true))
|
||||
(token(';' ~> OptSpace) flatMap { _ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace) } map (_.trim)).+ map { _.toList }
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ object BasicKeys {
|
|||
private[sbt] val classLoaderCache = AttributeKey[ClassLoaderCache]("class-loader-cache", "Caches class loaders based on the classpath entries and last modified times.", 10)
|
||||
private[sbt] val OnFailureStack = AttributeKey[List[Option[Exec]]]("on-failure-stack", "Stack that remembers on-failure handlers.", 10)
|
||||
private[sbt] val explicitGlobalLogLevels = AttributeKey[Boolean]("explicit-global-log-levels", "True if the global logging levels were explicitly set by the user.", 10)
|
||||
private[sbt] val templateResolvers = AttributeKey[Seq[TemplateResolver]]("templateResolvers", "List of template resolvers.", 1000)
|
||||
private[sbt] val templateResolverInfos = AttributeKey[Seq[TemplateResolverInfo]]("templateResolverInfos", "List of template resolver infos.", 1000)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,3 +279,5 @@ object State {
|
|||
private[sbt] def getBoolean(s: State, key: AttributeKey[Boolean], default: Boolean): Boolean =
|
||||
s.get(key) getOrElse default
|
||||
}
|
||||
|
||||
case class TemplateResolverInfo(module: ModuleID, implementationClass: String)
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ object Defaults extends BuildCommon {
|
|||
maxErrors :== 100,
|
||||
fork :== false,
|
||||
initialize :== {},
|
||||
templateResolvers :== Nil,
|
||||
templateResolverInfos :== Nil,
|
||||
forcegc :== sys.props.get("sbt.task.forcegc").map(java.lang.Boolean.parseBoolean).getOrElse(GCUtil.defaultForceGarbageCollection),
|
||||
minForcegcInterval :== GCUtil.defaultMinForcegcInterval,
|
||||
serverPort := 5000 + (Hash.toHex(Hash(appConfiguration.value.baseDirectory.toString)).## % 1000)
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
package sbt
|
||||
|
||||
import sbt.template.TemplateResolver
|
||||
|
||||
object Giter8TemplateResolver extends TemplateResolver {
|
||||
def isDefined(args0: Array[String]): Boolean =
|
||||
{
|
||||
val args = args0.toList filterNot { _.startsWith("-") }
|
||||
// Mandate .g8
|
||||
val Github = """^([^\s/]+)/([^\s/]+?)(?:\.g8)$""".r
|
||||
val Local = """^file://(\S+)(?:\.g8)(?:/)?$""".r
|
||||
object GitUrl {
|
||||
val NativeUrl = "^(git[@|://].*)$".r
|
||||
val HttpsUrl = "^(https://.*)$".r
|
||||
val HttpUrl = "^(http://.*)$".r
|
||||
val SshUrl = "^(ssh://.*)$".r
|
||||
def unapplySeq(s: Any): Option[List[String]] =
|
||||
NativeUrl.unapplySeq(s) orElse
|
||||
HttpsUrl.unapplySeq(s) orElse
|
||||
HttpUrl.unapplySeq(s) orElse
|
||||
SshUrl.unapplySeq(s)
|
||||
}
|
||||
args.headOption match {
|
||||
case Some(Github(_, _)) => true
|
||||
case Some(Local(_)) => true
|
||||
case GitUrl(uri) => uri contains (".g8")
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
def run(args: Array[String]): Unit =
|
||||
giter8.Giter8.run(args)
|
||||
}
|
||||
|
|
@ -404,7 +404,7 @@ object Keys {
|
|||
val sbtVersion = SettingKey[String]("sbt-version", "Provides the version of sbt. This setting should be not be modified.", AMinusSetting)
|
||||
val sbtBinaryVersion = SettingKey[String]("sbt-binary-version", "Defines the binary compatibility version substring.", BPlusSetting)
|
||||
val skip = TaskKey[Boolean]("skip", "For tasks that support it (currently only 'compile' and 'update'), setting skip to true will force the task to not to do its work. This exact semantics may vary by task.", BSetting)
|
||||
val templateResolvers = SettingKey[Seq[TemplateResolver]]("templateResolvers", "Template resolvers used for 'new'.", BSetting)
|
||||
val templateResolverInfos = SettingKey[Seq[TemplateResolverInfo]]("templateResolverInfos", "Template resolvers used for 'new'.", BSetting)
|
||||
|
||||
// special
|
||||
val sessionVars = AttributeKey[SessionVar.Map]("session-vars", "Bindings that exist for the duration of the session.", Invisible)
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ import sbt.internal.CommandStrings._
|
|||
import BasicCommandStrings._
|
||||
import BasicCommands._
|
||||
import CommandUtil._
|
||||
import TemplateCommandUtil.templateCommand
|
||||
|
||||
object BuiltinCommands {
|
||||
def initialAttributes = AttributeMap.empty
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import java.io.File
|
|||
import java.net.URI
|
||||
import java.util.Locale
|
||||
import Project.{ Initialize => _, Setting => _, _ }
|
||||
import Keys.{ appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, templateResolvers, serverPort, thisProject, thisProjectRef, watch }
|
||||
import Keys.{ appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, templateResolverInfos, serverPort, thisProject, thisProjectRef, watch }
|
||||
import Scope.{ GlobalScope, ThisScope }
|
||||
import Def.{ Flattened, Initialize, ScopedKey, Setting }
|
||||
import sbt.internal.{ Load, BuildStructure, LoadedBuild, LoadedBuildUnit, SettingGraph, SettingCompletions, AddSettings, SessionSettings, LogManager }
|
||||
|
|
@ -421,19 +421,15 @@ object Project extends ProjectExtra {
|
|||
val allCommands = commandsIn(ref) ++ commandsIn(BuildRef(ref.build)) ++ (commands in Global get structure.data toList)
|
||||
val history = get(historyPath) flatMap idFun
|
||||
val prompt = get(shellPrompt)
|
||||
val trs = (templateResolvers in Global get structure.data).toList.flatten
|
||||
val trs = (templateResolverInfos in Global get structure.data).toList.flatten
|
||||
val watched = get(watch)
|
||||
val port: Option[Int] = get(serverPort)
|
||||
val commandDefs = allCommands.distinct.flatten[Command].map(_ tag (projectCommand, true))
|
||||
val newDefinedCommands = commandDefs ++ BasicCommands.removeTagged(s.definedCommands, projectCommand)
|
||||
<<<<<<< HEAD
|
||||
val newAttrs0 = setCond(Watched.Configuration, watched, s.attributes).put(historyPath.key, history)
|
||||
val newAttrs = setCond(serverPort.key, port, newAttrs0)
|
||||
=======
|
||||
val newAttrs = setCond(Watched.Configuration, watched, s.attributes).
|
||||
put(historyPath.key, history).
|
||||
put(templateResolvers.key, trs)
|
||||
>>>>>>> 954e744... Adds templateResolvers and `new` command
|
||||
.put(historyPath.key, history)
|
||||
.put(templateResolverInfos.key, trs)
|
||||
s.copy(attributes = setCond(shellPrompt.key, prompt, newAttrs), definedCommands = newDefinedCommands)
|
||||
}
|
||||
def setCond[T](key: AttributeKey[T], vopt: Option[T], attributes: AttributeMap): AttributeMap =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import xsbti.AppConfiguration
|
||||
import sbt.classpath.ClasspathUtilities
|
||||
import BasicCommandStrings._
|
||||
import BasicKeys._
|
||||
import complete.{ Parser, DefaultParsers }
|
||||
import DefaultParsers._
|
||||
import Command.applyEffect
|
||||
import Path._
|
||||
|
||||
private[sbt] object TemplateCommandUtil {
|
||||
def templateCommand = Command.make(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser)
|
||||
def templateCommandParser(state: State) =
|
||||
{
|
||||
val p = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map { case _ => Nil })
|
||||
val infos = (state get templateResolverInfos) match {
|
||||
case Some(infos) => infos.toList
|
||||
case None => Nil
|
||||
}
|
||||
val log = state.globalLogging.full
|
||||
val extracted = (Project extract state)
|
||||
val (s2, ivyConf) = extracted.runTask(Keys.ivyConfiguration, state)
|
||||
val globalBase = BuildPaths.getGlobalBase(state)
|
||||
val ivyScala = extracted.get(Keys.ivyScala in Keys.updateSbtClassifiers)
|
||||
applyEffect(p)({ inputArg =>
|
||||
val arguments = inputArg.toList ++
|
||||
(state.remainingCommands.toList match {
|
||||
case "shell" :: Nil => Nil
|
||||
case xs => xs
|
||||
})
|
||||
run(infos, arguments, state.configuration, ivyConf, globalBase, ivyScala, log)
|
||||
"exit" :: s2.copy(remainingCommands = Nil)
|
||||
})
|
||||
}
|
||||
|
||||
private def run(infos: List[TemplateResolverInfo], arguments: List[String], config: AppConfiguration,
|
||||
ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): Unit =
|
||||
infos find { info =>
|
||||
val loader = infoLoader(info, config, ivyConf, globalBase, ivyScala, log)
|
||||
val hit = tryTemplate(info, arguments, loader)
|
||||
if (hit) {
|
||||
runTemplate(info, arguments, loader)
|
||||
}
|
||||
hit
|
||||
} match {
|
||||
case Some(_) => // do nothing
|
||||
case None => System.err.println("Template not found for: " + arguments.mkString(" "))
|
||||
}
|
||||
private def tryTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Boolean =
|
||||
{
|
||||
val resultObj = call(info.implementationClass, "isDefined", loader)(
|
||||
classOf[Array[String]]
|
||||
)(arguments.toArray)
|
||||
resultObj.asInstanceOf[Boolean]
|
||||
}
|
||||
private def runTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Unit =
|
||||
call(info.implementationClass, "run", loader)(classOf[Array[String]])(arguments.toArray)
|
||||
private def infoLoader(info: TemplateResolverInfo, config: AppConfiguration,
|
||||
ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): ClassLoader =
|
||||
ClasspathUtilities.toLoader(classpathForInfo(info, ivyConf, globalBase, ivyScala, log), config.provider.loader)
|
||||
private def call(interfaceClassName: String, methodName: String, loader: ClassLoader)(argTypes: Class[_]*)(args: AnyRef*): AnyRef =
|
||||
{
|
||||
val interfaceClass = getInterfaceClass(interfaceClassName, loader)
|
||||
val interface = interfaceClass.newInstance.asInstanceOf[AnyRef]
|
||||
val method = interfaceClass.getMethod(methodName, argTypes: _*)
|
||||
try { method.invoke(interface, args: _*) }
|
||||
catch {
|
||||
case e: java.lang.reflect.InvocationTargetException =>
|
||||
e.getCause match {
|
||||
case t => throw t
|
||||
}
|
||||
}
|
||||
}
|
||||
private def getInterfaceClass(name: String, loader: ClassLoader) = Class.forName(name, true, loader)
|
||||
|
||||
// Cache files under ~/.sbt/0.13/templates/org_name_version
|
||||
private def classpathForInfo(info: TemplateResolverInfo, ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): List[File] =
|
||||
{
|
||||
val updateUtil = new UpdateUtil(ivyConf, log)
|
||||
val templatesBaseDirectory = new File(globalBase, "templates")
|
||||
val templateId = s"${info.module.organization}_${info.module.name}_${info.module.revision}"
|
||||
val templateDirectory = new File(templatesBaseDirectory, templateId)
|
||||
def jars = (templateDirectory ** -DirectoryFilter).get
|
||||
if (!(info.module.revision endsWith "-SNAPSHOT") && jars.nonEmpty) jars.toList
|
||||
else {
|
||||
IO.createDirectory(templateDirectory)
|
||||
val m = updateUtil.getModule(info.module.copy(configurations = Some("component")), ivyScala)
|
||||
val xs = updateUtil.update(m, templateDirectory)(_ => true) match {
|
||||
case Some(xs) => xs.toList
|
||||
case None => Nil
|
||||
}
|
||||
xs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,10 @@ object Giter8TemplatePlugin extends AutoPlugin {
|
|||
|
||||
override lazy val globalSettings: Seq[Setting[_]] =
|
||||
Seq(
|
||||
templateResolvers += Giter8TemplateResolver
|
||||
templateResolverInfos +=
|
||||
TemplateResolverInfo(
|
||||
ModuleID("org.scala-sbt.sbt-giter8-resolver", "sbt-giter8-resolver", "0.1.0") cross CrossVersion.binary,
|
||||
"sbtgiter8resolver.Giter8TemplateResolver"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ object Dependencies {
|
|||
val specs2 = "org.specs2" %% "specs2" % "2.4.17"
|
||||
val junit = "junit" % "junit" % "4.11"
|
||||
val templateResolverApi = "org.scala-sbt" % "template-resolver" % "0.1"
|
||||
val giter8 = "org.foundweekends.giter8" %% "giter8" % "0.7.0"
|
||||
|
||||
private def scala211Module(name: String, moduleVersion: String) = Def setting (
|
||||
scalaBinaryVersion.value match {
|
||||
|
|
|
|||
Loading…
Reference in New Issue