5

In our project, we have a enhance post-process to the .class files generated by compile. This enhance step actually modifies the generated .class files then overrides it.

enhance <<= enhance triggeredBy (compile in Compile)

The problem is that sbt has a mechanism called incremental recompilation. It monitors the generated .class file. Every time the enhancer overrides the generated .class file, sbt recognises these modifications and recompiles related sources in next compile command.

To us, a recompilation is a very time-consuming work. We want to stop sbt from recompiling modified .class file. That may mean making sbt only monitor source changes, not output changes.

I did some searching on this. But I found little things about this. Now I know a trait called Analysis is likely responsible for the mapping from sources to output .class files. So I ask help from you guys.

Ps: we may solve this problem by putting the output of enhance to another folder, but it is not preferred.

Jacek Laskowski
  • 72,696
  • 27
  • 242
  • 420
Chenyu
  • 171
  • 6
  • Create separate project with compiled classes and add it as dependency (library) to your play project. – Andrzej Jozwik Sep 23 '14 at 13:45
  • @AndrzejJozwik Thank you for your comment. The way you suggest is not preferred, because it is a long way to our target. The increased complexity may cause more problems due to our project is a very large project. We would like a way to solve the problem directly. – Chenyu Sep 23 '14 at 14:29

3 Answers3

4

sbt strongly discourages mutations to files. You should generate different files instead. By doing so, you will solve your problem, since the incremental compiler of sbt will still be looking at the unaltered .class files. You will have some rewiring to do:

Send the outputs of the compile task somewhere else:

classDirectory in Compile := crossTarget.value / "classes-orig"

Processing these .class files with your tool, and send them to crossTarget.value / "classes" (the original classDirectory:

enhance <<= enhance triggeredBy (compile in Compile)

enhance := {
  val fromDir := (classesDirectory in Compile).value
  val toDir := crossTarget.value / "classes"
  ...
}

Rewire productDirectories to use crossTarget.value / "classes" anyway (otherwise it'll go looking in your modified classDirectory:

productDirectories in Compile := Seq(crossTarget.value / "classes")

Make sure that products depends on your enhance task:

products in Compile <<= (products in Compile) dependsOn enhance

You may need some more rewiring if you have resources (see copyResources). But basically you should be able to get there.

sjrd
  • 21,805
  • 2
  • 61
  • 91
2

I said about that sbt monitors the output .class file. When a .class file is modified, it recompiles the .class file's source.

After some research, we found that sbt notices file's modification by its last modified time. That is to say, we can fool sbt by rolling back the last modified time after the modification, so that sbt won't notice any changes.

So, our solution is simple but effective:

  1. find all .class files
  2. note down their last modified time
  3. do the enhance
  4. put back the former last modified time

This is a small trick. We still expect more robust solutions.

Chenyu
  • 171
  • 6
0

Description:

A little like Chenyu, I had to write a plugin for SBT 1.x, that would enhance compiled classes and later I wanted to make sure that those enhanced classes were used for building the jar.

I did not want to hack this solution, so Chenyu's answer was not acceptable to me and sjrd's answer was very helpful but adjusted to SBT 0.13.

So here is my working solution, with little comments:

Code:

object autoImport {
  val enhancedDest = settingKey[File]("Output dir for enhanced sources")
}

def enhanceTask: Def.Initialize[Task[Unit]] = Def.task {
  val inputDir = (classDirectory in Compile).value
  val outputDir = enhancedDest.value
  enhance(inputDir, outputDir)
  ...
}

override def projectSettings: Seq[Def.Setting[_]] = Seq(
  enhancedDest := crossTarget.value / "classes-enhanced",
  products in Compile := Seq(enhancedDest.value), // mark enhanced folder to use for packaging

  // https://www.scala-sbt.org/1.0/docs/Howto-Dynamic-Task.html#build.sbt+v2
  compile in Compile := Def.taskDyn {
    val c = (compile in Compile).value // compile 1st.
    Def.task {
      (copyResources in Compile).value // copy resources before enhance        
      enhanceTask.value                // enhance
      c
    }
  }.value
)
Atais
  • 10,857
  • 6
  • 71
  • 111