downloading and running app on the side with sbt-sidedish
I've been asked by a few people on downloading JARs, and then running them from an sbt plugin.
Most recently, Shane Delmore (@shanedelmore) asked me about this at nescala in Brooklyn.
During an unconference session I hacked together a demo, and I continued some more after I came home.
sbt-sidedish
sbt-sidedish is a toolkit for plugin authors to download and run an app on the side from a plugin.
It on its own does not define any plugins.
rewritedemo, a commandline app
First, create a command line app that you want to run on the side. This could be in Scala 2.11 or 2.12.
Here's a demo app I wrote that uses Scalafix to add an import statement to some code. Scalafix is a Scala code rewriting tool and library that uses scala.meta. See Scalafix docs and sources for the details on that.
sbt-rewritedemo, an sbt plugin
Next, suppose you want to run rewritedemo against some subproject in your build and derive another subproject.
Here's a plugin you can write using sbt-sidedish.
package sbtrewritedemo import sbt._ import Keys._ import sbtsidedish.Sidedish object RewriteDemoPlugin extends AutoPlugin { override def requires = sbt.plugins.JvmPlugin object autoImport extends RewriteDemoKeys import autoImport._ val sidedish = Sidedish("sbtrewritedemo-metatool", file("sbtrewritedemo-metatool"), // scalaVersion "2.12.1", // ModuleID of your app List("com.eed3si9n" %% "rewritedemo" % "0.1.2"), // main class "sbtrewritedemo.RewriteApp") override def extraProjects: Seq[Project] = List(sidedish.project // extra settings .settings( // Resolve the app from sbt community repo. resolvers += Resolver.bintrayIvyRepo("sbt", "sbt-plugin-releases") )) override def projectSettings = Seq( rewritedemoOrigin := "example", sourceGenerators in Compile += Def.sequential( Def.taskDyn { val example = LocalProject(rewritedemoOrigin.value) val workingDir = baseDirectory.value val out = (sourceManaged in Compile).value / "rewritedemo" Def.taskDyn { val srcDirs = (sourceDirectories in (example, Compile)).value val srcs = (sources in (example, Compile)).value val cp = (fullClasspath in (example, Compile)).value val jvmOptions = List("-Dscalameta.sourcepath=" + "\"" + srcDirs.mkString(java.io.File.pathSeparator) + "\"", "-Dscalameta.classpath=" + "\"" + cp.mkString(java.io.File.pathSeparator)+ "\"", "-Drewrite.out=" + out) Def.task { sidedish.forkRunTask(workingDir, jvmOptions = jvmOptions, args = Nil).value } } }, Def.task { val out = (sourceManaged in Compile).value / "rewritedemo" (out ** "*.scala").get } ).taskValue ) } trait RewriteDemoKeys { val rewritedemoOrigin = settingKey[String]("") } object RewriteDemoKeys extends RewriteDemoKeys
This uses synthetic subproject feature that was added in sbt 0.13.13.
The tricky part is passing the right arguments to the app from the user's build. I had to double wrap dynamic tasks to refer to the source directories from the origin and pass it in as JVM options.
how sbt-rewritedemo gets used
build.properties:
sbt.version=0.13.13
plugins.sbt:
addSbtPlugin("com.eed3si9n" % "sbt-rewritedemo" % "0.1.2")
build.sbt:
lazy val example = (project in file("example")) .settings( name := "example", scalaVersion := "2.12.1" ) lazy val derived1 = (project in file("derived1")) .enablePlugins(RewriteDemoPlugin) .settings( name := "derived1", rewritedemoOrigin := "example", scalaVersion := "2.12.1" )
Now suppose we have Example.scala
under example/src/main/scala/Example.scala
:
package foo object Example extends App { println(Seq(1, 2, 3)) }
When you run derived1/compile
from the sbt shell, it runs the rewrite app using Scala 2.12 and generates the following file under the managed source directory:
package foo import scala.collection.immutable.Seq object Example extends App { println(Seq(1, 2, 3)) }
In other words, using sbt-sidedish we could run 2.12 app from an sbt plugin.
- Login to post comments