Run custom task automatically before/after standard task
Asked Answered
M

2

36

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 ?.

Macur answered 19/10, 2011 at 11:58 Comment(1)
Possible duplicate of https://mcmap.net/q/428034/-sbt-before-after-hooks-for-a-task/1305344. Definitely needs merging.Seibel
S
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).

Socialminded answered 19/10, 2011 at 13:22 Comment(9)
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).Macur
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...Socialminded
Works like a charm also nice that your answer shows the two ways around this (run something after or before a existing task). Thanks!Macur
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 may also apply here.Nelsonnema
does the compile in Compile technique still work in recent versions of sbt?On
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?Rescissory
hmm, workaround for replacing existing Tasks 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.Rescissory
@PaulButcher: can you please explain what the magic <<= does? I am unable to find a reference for it in SBT's docs.Georginageorgine
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.Socialminded
G
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.

Garboard answered 22/3, 2019 at 11:28 Comment(3)
How would you adapt it to a multiple projects in a single build? (ThisBuild / compile in Compile) := ... isn't working for meRations
@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!Garboard
@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.Rations

© 2022 - 2024 — McMap. All rights reserved.