Create script with classpath from SBT
I'd like to have SBT create a file and write the project's runtime full classpath (scala, managed and unmanaged libs, project classes) for a particular stage (in this case, only for compile
).
I'm trying to replicate something I did with Maven, using the maven-antrun-plugin
:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>generate-runner</id>
<phase>packag开发者_JS百科e</phase>
<configuration>
<target>
<property name="runtime_classpath" refid="maven.runtime.classpath" />
<property name="runtime_entrypoint" value="com.biasedbit.webserver.Bootstrap" />
<echo file="../../bin/run-server.sh" append="false">#!/bin/sh
java -classpath ${runtime_classpath} ${runtime_entrypoint} $$*
</echo>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
How can I do this with SBT?
The fundamentals are right in David's answer. There are some small ways it can be improved. The java launcher can be used directly because the Scala library is included on the classpath. sbt can autodetect the main class if there is only one defined. sbt also has some methods that can make working with files easier, such as the utility methods in sbt.IO.
TaskKey[File]("mkrun") <<= (baseDirectory, fullClasspath in Runtime, mainClass in Runtime) map { (base, cp, main) =>
val template = """#!/bin/sh
java -classpath "%s" %s "$@"
"""
val mainStr = main getOrElse error("No main class specified")
val contents = template.format(cp.files.absString, mainStr)
val out = base / "../../bin/run-server.sh"
IO.write(out, contents)
out.setExecutable(true)
out
}
This can go in your build.sbt
directly. Alternatively, define the key separately and put it in project/Build.scala
:
import sbt._
import Keys._
object MyBuild extends Build {
val mkrun = TaskKey[File]("mkrun")
lazy val proj = Project("demo", file(".")) settings(
mkrun <<= ... same argument as above ...
)
}
You can create a task to create a file to launch the app. @Kipton Barros posted this in How do I run an sbt main class from the shell as normal command-line program?:
val MkUnixlauncher = config("mkunixlauncher") extend(Compile)
val mkunixlauncher = TaskKey[Unit]("mkunixlauncher")
val mkunixlauncherTask = mkunixlauncher <<= (target, fullClasspath in Runtime) map { (target, cp) =>
def writeFile(file: File, str: String) {
val writer = new PrintWriter(file)
writer.println(str)
writer.close()
}
val cpString = cp.map(_.data).mkString(System.getProperty("path.separator"))
val runtime_entrypoint = "com.biasedbit.webserver.Bootstrap"
val launchString = """
CLASSPATH="%s"
scala -usejavacp -Djava.class.path="${CLASSPATH}" %s "$@"
""".format(cpString, entrypoint)
val targetFile = (target / "run-server.sh").asFile
writeFile(targetFile, launchString)
targetFile.setExecutable(true)
}
This creates a file named run-server.sh in your target directory that has the classpath set properly to run the app. Add mkunixlauncherTask to your buildsettings in Build.scala (or build.sbt) and then you can give the "mkunixlauncher" command to create the script.
Tweak to taste.
Just discovered the sbt start script plugin: https://github.com/typesafehub/xsbt-start-script-plugin:
This plugin allows you to generate a script target/start for a project. The script will run the project "in-place" (without having to build a package first).
The target/start script is similar to sbt run but it doesn't rely on SBT. sbt run is not recommended for production use because it keeps SBT itself in-memory. target/start is intended to run an app in production.
The plugin adds a task start-script which generates target/start. It also adds a stage task, aliased to the start-script task.
精彩评论