Add debug logging in ClassfileManager

Add logging of various operations the transactional class file manager is
doing. You can pass logger to be used by the transactional class file
manager by using overloaded definition of `ClassfileManager.transactional`
method. The old overload has been deprecated.

The factory methods for class file manager in IncOptions companion object
has been deprecated in favor of using ClassfileManager companion object
directly. The code in Defaults.scala has been updated to use non-deprecated
methods. The logging is turned off by default.

The canonical way of enabling transactional class file manager in sbt
project is:

```
incOptions := incOptions.value.withNewClassfileManager(
  sbt.inc.ClassfileManager.transactional(
    crossTarget.value / "classes.bak",
    (streams in (compile, Compile)).value.log
  )
)
```

It's a bit verbose which shows that the api for this is not the best.
However, I don't expect sbt users to need this code very often.

This patch should help debug the problem described in #1184
This commit is contained in:
Grzegorz Kossakowski 2014-04-11 13:42:21 +02:00
parent 10afd46785
commit a7fb54e4df
3 changed files with 28 additions and 8 deletions

View File

@ -13,7 +13,7 @@ trait ClassfileManager
* Any empty ancestor directories of deleted files must not exist either.*/
def delete(classes: Iterable[File]): Unit
/** Called once per compilation step with the class files generated during that step.*/
/** Called once per compilation step with the class files generated during that step.*/
def generated(classes: Iterable[File]): Unit
/** Called once at the end of the whole compilation run, with `success` indicating whether compilation succeeded (true) or not (false).*/
@ -29,29 +29,45 @@ object ClassfileManager
def generated(classes: Iterable[File]) {}
def complete(success: Boolean) {}
}
@deprecated("Use overloaded variant that takes additional logger argument, instead.", "0.13.5")
def transactional(tempDir0: File): () => ClassfileManager =
transactional(tempDir0, sbt.Logger.Null)
/** When compilation fails, this ClassfileManager restores class files to the way they were before compilation.*/
def transactional(tempDir0: File): () => ClassfileManager = () => new ClassfileManager
def transactional(tempDir0: File, logger: sbt.Logger): () => ClassfileManager = () => new ClassfileManager
{
val tempDir = tempDir0.getCanonicalFile
IO.delete(tempDir)
IO.createDirectory(tempDir)
logger.debug(s"Created transactional ClassfileManager with tempDir = $tempDir")
private[this] val generatedClasses = new mutable.HashSet[File]
private[this] val movedClasses = new mutable.HashMap[File, File]
private def showFiles(files: Iterable[File]): String = files.map(f => s"\t$f").mkString("\n")
def delete(classes: Iterable[File])
{
for(c <- classes) if(c.exists && !movedClasses.contains(c) && !generatedClasses(c))
logger.debug(s"About to delete class files:\n${showFiles(classes)}")
val toBeBackedUp = classes.filter(c => c.exists && !movedClasses.contains(c) && !generatedClasses(c))
logger.debug(s"We backup classs files:\n${showFiles(toBeBackedUp)}")
for(c <- toBeBackedUp) {
movedClasses.put(c, move(c))
}
IO.deleteFilesEmptyDirs(classes)
}
def generated(classes: Iterable[File]): Unit = generatedClasses ++= classes
def generated(classes: Iterable[File]): Unit = {
logger.debug(s"Registering generated classes:\n${showFiles(classes)}")
generatedClasses ++= classes
}
def complete(success: Boolean)
{
if(!success) {
logger.debug("Rolling back changes to class files.")
logger.debug(s"Removing generated classes:\n${showFiles(generatedClasses)}")
IO.deleteFilesEmptyDirs(generatedClasses)
logger.debug(s"Restoring class files: \n${showFiles(movedClasses.map(_._1))}")
for( (orig, tmp) <- movedClasses ) IO.move(tmp, orig)
}
logger.debug(s"Removing the temporary directory used for backing up class files: $tempDir")
IO.delete(tempDir)
}

View File

@ -234,9 +234,12 @@ object IncOptions extends Serializable {
private def readResolve(): Object = IncOptions
//- EXPANDED CASE CLASS METHOD END -//
def defaultTransactional(tempDir: File): IncOptions = setTransactional(Default, tempDir)
@deprecated("Use IncOptions.Default.withNewClassfileManager(ClassfileManager.transactional(tempDir)), instead.", "0.13.5")
def defaultTransactional(tempDir: File): IncOptions =
setTransactional(Default, tempDir)
@deprecated("Use opts.withNewClassfileManager(ClassfileManager.transactional(tempDir)), instead.", "0.13.5")
def setTransactional(opts: IncOptions, tempDir: File): IncOptions =
opts.copy(newClassfileManager = ClassfileManager.transactional(tempDir))
opts.withNewClassfileManager(ClassfileManager.transactional(tempDir, sbt.Logger.Null))
private val transitiveStepKey = "transitiveStep"
private val recompileAllFractionKey = "recompileAllFraction"

View File

@ -215,7 +215,8 @@ object Defaults extends BuildCommon
)
def compileBase = inTask(console)(compilersSetting :: Nil) ++ compileBaseGlobal ++ Seq(
incOptions := IncOptions.setTransactional(incOptions.value, crossTarget.value / "classes.bak"),
incOptions := incOptions.value.withNewClassfileManager(
sbt.inc.ClassfileManager.transactional(crossTarget.value / "classes.bak", sbt.Logger.Null)),
scalaInstance <<= scalaInstanceTask,
crossVersion := (if(crossPaths.value) CrossVersion.binary else CrossVersion.Disabled),
crossTarget := makeCrossTarget(target.value, scalaBinaryVersion.value, sbtBinaryVersion.value, sbtPlugin.value, crossPaths.value)