35

I often want to do some customization before one of the standard tasks are run. I realize I can make new tasks that executes existing tasks in the order I want, but I find that cumbersome and the chance that a developer misses that he is supposed to run my-compile instead of compile is big and leads to hard to fix errors.

So I want to define a custom task (say prepare-app) and inject it into the dependency tree of the existing tasks (say package-bin) so that every time someone invokes package-bin my custom tasks is run right before it.

I tried doing this

  def mySettings = {
    inConfig(Compile)(Seq(prepareAppTask <<= packageBin in Compile map { (pkg: File) =>
      // fiddle with the /target folder before package-bin makes it into a jar
    })) ++
    Seq(name := "my project", version := "1.0")
  }

  lazy val prepareAppTask = TaskKey[Unit]("prepare-app")

but it's not executed automatically by package-bin right before it packages the compile output into a jar. So how do I alter the above code to be run at the right time ?

More generally where do I find info about hooking into other tasks like compile and is there a general way to ensure that your own tasks are run before and after a standard tasks are invoked ?.

Lars Tackmann
  • 20,275
  • 13
  • 66
  • 83

2 Answers2

39

Extending an existing task is documented the SBT documentation for Tasks (look at the section Modifying an Existing Task).

Something like this:

compile in Compile <<= (compile in Compile) map { _ => 
  // what you want to happen after compile goes here 
}

Actually, there is another way - define your task to depend on compile

prepareAppTask := (whatever you want to do) dependsOn compile

and then modify packageBin to depend on that:

packageBin <<= packageBin dependsOn prepareAppTask

(all of the above non-tested, but the general thrust should work, I hope).

Hosam Aly
  • 41,555
  • 36
  • 141
  • 182
Paul Butcher
  • 10,722
  • 3
  • 40
  • 44
  • Thanks for the link I tried doing this but its not immediately obvious how to make it work. I need two things. I need to define a new task that does my custom work after compile has run and I need to modify package-bin from Compile to depend on this custom task (i.e. my task pipeline should be compile -> my-task -> package-bin). – Lars Tackmann Oct 19 '11 at 13:40
  • No you don't - you need to add the code for your new task to a re-defined compile task which depends on the previous compile task. I'll see if I can knock together an example and add it to my answer... – Paul Butcher Oct 19 '11 at 13:46
  • Works like a charm also nice that your answer shows the two ways around this (run something after or before a existing task). Thanks! – Lars Tackmann Oct 19 '11 at 14:03
  • 3
    This is correct for inserting an arbitrary action. However, the action usually isn't arbitrary. Depending on what you want to do, the answer to your other question on [excluding classes from the jar](http://stackoverflow.com/q/7819066/850196) may also apply here. – Mark Harrah Oct 22 '11 at 21:59
  • does the `compile in Compile` technique still work in recent versions of sbt? – matanster Oct 05 '15 at 11:27
  • I'm struggling to be able to do this by default for projects in the `projectSettings` of an `AutoPlugin` (although it does work when the user does it explicitly per-`Project`). Any ideas? – fommil Dec 05 '15 at 14:53
  • hmm, workaround for replacing existing `Task`s in an `AutoPlugin` seems to be to put them into a list and then the users have to manually add that list to each project. – fommil Dec 05 '15 at 15:02
  • @PaulButcher: can you please explain what the magic `<<=` does? I am unable to find a reference for it in SBT's docs. – Dan Barowy Feb 24 '16 at 20:09
  • 3
    Ah - looks like SBT's moved on somewhat since I wrote this answer. the `<<=` operator used to be the way that you did things, but as you say it's no longer in the documentation. I'm afraid that I've been out of the Scala world for a while and I'm not sure how best to update this answer to the new way of things. – Paul Butcher Feb 26 '16 at 11:57
7

As an update for the previous answer by @Paul Butcher, this could be done in a bit different way in SBT 1.x versions since <<== is no longer supported. I took an example of a sample task to run before the compilation that I use in one of my projects:

lazy val wsdlImport = TaskKey[Unit]("wsdlImport", "Generates Java classes from WSDL")

wsdlImport := {
  import sys.process._
  "./wsdl/bin/wsdl_import.sh" !
  // or do whatever stuff you need
}

(compile in Compile) := ((compile in Compile) dependsOn wsdlImport).value

This is very similar to how it was done before 1.x.

Also, there is another way suggested by official SBT docs, which is basically a composition of tasks (instead of dependencies hierarchy). Taking the same example as above:

(compile in Compile) := {
  val w = wsdlImport.value
  val c = (compile in Compile).value
  // you can add more tasks to composition or perform some actions with them
  c
}

It feels like giving more flexibility in some cases, though the first example looks a bit neater, as for me.

Tested on SBT 1.2.3 but should work with other 1.x as well.

Vladimir Salin
  • 2,951
  • 2
  • 36
  • 49
  • How would you adapt it to a multiple projects in a single build? `(ThisBuild / compile in Compile) := ...` isn't working for me – Troy Daniels Jun 05 '19 at 21:06
  • @Troy Daniels I'm not sure about your exact case, but generally, sbt suggests using `dependsOn` for setting on dependencies between modules. E.g. an example I use in one of my projects is: 1) I have a custom task to be run before `compile` in a sub-project called `wsdl`, and 2) for my main `build.sbt`, I use `val root = (project in file(".")).aggregate(wsdl).dependsOn(wsdl)...` so the custom task is executed before compiling main project. HTH! – Vladimir Salin Jun 06 '19 at 10:02
  • 1
    @Vladimar Salin I have the inter project dependencies working correctly. It is the intra-project task dependencies that I want to change. As part of the `compile` task, I want to run another task that runs a style checker. Short of repeating `(prj1 / compile in Compile) = ...` for each project, I'm not seeing a way to run the stylechecker in every project. – Troy Daniels Jun 07 '19 at 14:45