I have two separated git repos, one holds a scala server built with sbt, the other holds a webapp frontend built with npm/bower/gulp.
In server repo, I already have a task to build a standalone jar (not the standard package task) ; in frontend repo, I can build with npm install && npm run build which would result into a standalone directory _public.
Now I would like to include the UI directory _public during sbt building jar task, I am wondering if there is a better way to do so other than manually spawn external process in sbt to call npm ?
The current state of sbt-web and webjars relying on it is that they are hardly keeping up with the growth of Node.js and npm. For example, the sbt-hbs plugin is no longer maintained, and my experience shows that it won't work with any Node.js version 8 or above. Support for sbt 1.0 is also not complete in some of these webjar-based frontent tools.
As such, unlike what the question suggests, directly spawning npm processes via sbt to build the frontend is a better solution than many.
This answer from a different question provides a sufficiently reliable way to call npm from sbt, which I will only replicate here for completeness.
buildFrontend := {
val s: TaskStreams = streams.value
val shell: Seq[String] = if (sys.props("os.name").contains("Windows")) Seq("cmd", "/c") else Seq("bash", "-c")
val npmInstall: Seq[String] = shell :+ "npm install"
val npmTest: Seq[String] = shell :+ "npm run test"
val npmLint: Seq[String] = shell :+ "npm run lint"
val npmBuild: Seq[String] = shell :+ "npm run build"
s.log.info("building frontend...")
if((npmInstall #&& npmTest #&& npmLint #&& npmBuild !) == 0) {
s.log.success("frontend build successful!")
} else {
throw new IllegalStateException("frontend build failed!")
}
}
If your frontend uses NPM and Gulp to build the app, you need to run it with a NodeJS engine (or maybe JVM engines like Rhino or Nashorn can do it? not sure) and it requires an external process.
The question to ask yourself is: do you really want to couple the deployment of your backend from the deployment of your frontend? Isn't there any case where you simply want to deploy one and not the other?
I think using SBT to deploy you frontend is nice but if your frontend is complex, you'd rather keep it separate from SBT.
Your JS app does not necessarily need to be served as a Play public asset, you could simply deploy it to its own place and reference it inside a Play HTML template.
I agree with Sebastien to keep the front-end dev (and possibly even deploy) separate from your back-end, as I am in the process of learning that lesson myself.
That said, have a look at SbtWeb (task workflow) in tandem with WebJars (package management) . SbtWeb has several plugins that can cover the basics (uglify, concat, filter), and in some cases I think if node is installed it can use it directly.
Related
I have 2 sbt projects that are runnable (akka app and another play application).
Is it possible to run both of them, and use ~reStart so they refresh on any changes to my project?
Any tips on doing this correctly so I don't run out of memory also?
If you are using Play Framework's latest version you can ~run without any plugin. Regarding standalone akka application you may use a library called sbt-revolver
runAkkaServer := {
(reStart in Compile in `akka-server`).evaluated
}
runWebServer := {
(~run in Compile in `web-server`).evaluated
}
mainClass in reStart := Some("com.example.MainAkka")
val runAkkaServer = inputKey[Unit]("Runs akka-server")
val runWebServer = inputKey[Unit]("Runs web-server")
NOTE: you can run both applications in restart mode without custom tasks:
1. ~run - Play server
2. reStart - Standalone
UPDATE:
I've tried to use following command to both of them, it seems that sbt-revolver is kinda trick and killing application onstart. When replace reStart with run it works perfect, but doesn't trigger changes.
screen -d -m sbt runAppServer; screen -d -m sbt runWebServer
So above code just doesnot behave as expected. Instead of custom tasks, we can run them in separate windows like this:
screen -dmS "appserver" sh -c "sbt 'project appserver;~reStart'; exec bash" ; screen -dmS "webserver" sh -c "sbt runWebServer; exec bash"
Also sbt runWebServer can be replaced by sbt 'project anothersubmodule;~run' if you wish.
I've created a simple demonstration project, you can find here
In order to start you can call just: ./starter.sh
NOTE: you can install screen command if you don't have easily.
Open two different Terminal tabs; cd into the specific directory in each tab and then run with SBT.
sbt run
For multiple web apps, specify a different port:
sbt run -Dhttp.port=8888
I am creating a specialized build task in my project that would require user input in STDIN which will let users select which version of hadoop,spark etc libraries will be used to build the application.
so far I have created a that task looks like the below.
lazy val build = SettingKey[Unit]("build", "build the app with all dependencies") in all // all is a project key
build := {
println(s"input hadoop client version")
val hadoop = scala.io.Source.fromInputStream(System.in).bufferedReader().readLine
println(s"input hiveserver jdbc version")
val hiveserver = scala.io.Source.fromInputStream(System.in).bufferedReader().readLine
// bunch of code to customize the build
}
The problem with the above code is that the body of build task key is run whenever the project is initialized in sbt or whenever reload task is run. so whenever I open the project in sbt, it hangs waiting for me to enter the version inputs in the standard input. How the task be made such that its body is executed only when the task is run and not when the build is initialized
I would consider defining a new custom task [http://www.scala-sbt.org/0.12.2/docs/Getting-Started/Custom-Settings.html#implementing-a-task] rather than extending build with blocking commands such as user input. As you experienced build is executed quite often.
You could for example extend build to use predefined values, read from environment settings. And have a interactiveBuild task where you prompt the user, showing the defaults retrieved from the env vars.
For more info about defining custom tasks check these out:
https://github.com/earldouglas/sbt-custom-task/tree/master/project
SBT plugin - User defined configuration for Command via their build.sbt
I want to publish a library, which has some usage examples in runnable classes. When I call sbt run , it finds them and asks me, which of the main classes found I want, and then launches it. That's neat, I'd like this behaviour to stay. But those examples complicate my Android build ( more proguard configs ), so I don't want them in published artefacts.
For now, I totally exclude them, putting this into build.sbt :
excludeFilter in Compile ~= { _ ||
new FileFilter {
def accept(f: File) = f.getPath.containsSlice("/examples/")
} }
then, when I run sbt publish-local, I get jars without examples, but then one can't get the library source and see how it works, with just typing sbt run. How can I exclude examples package only from publishing, but let it still be compiled for local runs?
I'd recommend splitting examples into another subproject instead.
I use sbt in the following fashion: I run ~ test:compile in sbt and then work in IDE, watching occasionaly if the project still compiles, because the IDE's presentation compiler tends to be buggy. When I git pull some code, there might be changes in the project/ files, so I want to have reload. Is there a way, how to watch both source files and project files, so when there is change in project files, I actually get the update?
As jsuereth explained this isn't a task SBT can handle in 1 instance. What's required is a reboot of SBT to abort the watching process and reload it's own configuration.
The following Scala script does exactly this, it uses Java NIO WatchService and Scala Process to monitor a path and restart a process. The code should be fairly simple to understand:
#!/usr/bin/env scala
import java.nio.file._
import scala.collection.JavaConversions._
import scala.sys.process._
val file = Paths.get(args(0))
val cmd = args(1)
val watcher = FileSystems.getDefault.newWatchService
file.register(
watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE
)
def exec = cmd run true
#scala.annotation.tailrec
def watch(proc: Process): Unit = {
val key = watcher.take
val events = key.pollEvents
val newProc =
if (!events.isEmpty) {
proc.destroy()
exec
} else proc
if (key.reset) watch(newProc)
else println("aborted")
}
watch(exec)
Usage in your sbt dir would be:
watchr.scala project/ "sbt ~ test:compile"
If anything is unclear don't hesitate to ask, of course any scripting language could be used to implement this behavior.
You actually can't use ~ <task> and have it rebuild the project itself right now, because ~ <task> needs to read the build definition itself to determine:
What source files to watch
How to run the task.
What you're doing is altering the config whe project/ changes. This requires a full reload or reboot of sbt to pull in the new configuration.
So, as of sbt 0.13, this isn't possible. You can have it so it will rebuild your source code when project/ changes, but without rebuilding the build definition, not much help.
You could create a new sbt prompt, or task, that when run could check to see if source files in project/ are updated and issue a warning/error so you know to reboot. That's probably the best option right now.
I'm trying to get sbt to compile and build some benchmarks. I've told it to add the benchmarks to the test path so they're recompiled along with tests, but I can't figure out how to write an action to let me actually run them. Is it possible to invoke classes from the Project definition class, or even just from the command line?
Yes, it is.
If you'd like to run them in the same VM the SBT is run in, then write a custom task similar to the following in your project definition file:
lazy val benchmark = task {
// code to run benchmarks
None // Some("will return an error message")
}
Typing benchmark in SBT console will run the task above. To actually run the benchmarks, or, for that matter, any other class you've compiled, you can reuse some of the existing infrastructure of SBT, namely the method runTask which will create a task that runs something for you. It has the following signature:
def runTask(mainClass: => Option[String], classpath: PathFinder, options: String*): Task
Simply add the following to your file:
lazy val benchmark = task { args =>
runTask(Some("whatever.your.mainclass.is"), testClasspath, args)
}
When running benchmarks, it is sometimes recommended that you run them in a separate jvm invocation, to get more reliable results. SBT allows you to run separate processes by invoking a method ! on a string command. Say you have a command java -jar path-to-artifact.jar you want to run. Then:
"java -jar path-to-artifact.jar" !
runs the command in SBT. You want to put the snippet above in a separate task, same as earlier.
And don't forget to reload when you change your project definition.
Couldn't you simply write the benchmarks as tests, so they will be run when you call 'test' in SBT?
You could also run a specific test with 'test-only', or run a main with 'run' or 'exec' (see http://code.google.com/p/simple-build-tool/wiki/RunningSbt for details).