summaryrefslogblamecommitdiff
path: root/core/src/main/scala/mill/eval/Evaluator.scala
blob: 19fbb6370f1b93eb6cd8d4af248704a43c55c82b (plain) (tree)
1
2
3
4
5
6
7
8
9
                 
 
                              
 
                     
                                          
                                        
                           
                                          
                                                  
                

                  
                               
 
                                    
                                                            

                                                    
 
                                                           
                        
 


                                                                     


                                                                                          
 
                                             
                                                                   
                                                                                     
 
                                                   
                                                           
                 
              

                                 
       


                              


                                                 
 
                                                                                                 
                                         
                                                                                           
     
                                                                                 

   
                                                              




                                                                 

                                                                                 

   
                                                                       
                                               

                                                                                                                                                
 
 
                                                                                 
 
                                            

                                                                                  


                                                                                     
        

     
                    
                                                     

                                                            
 
                   

                                                                                     









                                                                                                                        
                                                                              




                                                                                          





                                                                                           
                                                             




                                                           
 







                                                                                                         


                                       




                                                                               


             
 
                                      
         

     
 
 
                                         
                                                                  
                                                 
                                                         
 
 
                                                    
                                                                      










                                                                         
                                                       

     

                                                   
                                         
 
                                 


                                                      
 


                                                                            


                                           
                       
           
 


                              



                                                        






                                     
         
 
                              
     
 

                       
                              
   
 

















                                                             
 


                 
 





                                                                                              
 
package mill.eval

import java.net.URLClassLoader

import ammonite.ops._
import ammonite.runtime.SpecialClassLoader
import mill.define.{Graph, Target, Task}
import mill.discover.Mirror
import mill.discover.Mirror.LabelledTarget
import mill.discover.Mirror.Segment.{Cross, Label}
import mill.util
import mill.util._

import scala.collection.mutable

class Evaluator(workspacePath: Path,
                labeling: Map[Target[_], LabelledTarget[_]],
                log: Logger,
                sel: List[Mirror.Segment] = List()){

  def evaluate(goals: OSet[Task[_]]): Evaluator.Results = {
    mkdir(workspacePath)

    val transitive = Graph.transitiveTargets(goals)
    val topoSorted = Graph.topoSorted(transitive)
    val sortedGroups = Graph.groupAroundImportantTargets(topoSorted){
      case t: Target[_] if labeling.contains(t) || goals.contains(t) => Right(labeling(t))
      case t if goals.contains(t) => Left(t)
    }

    val evaluated = new OSet.Mutable[Task[_]]
    val results = mutable.LinkedHashMap.empty[Task[_], Result[Any]]
    val classLoaderSignatureCache = mutable.Map.empty[ClassLoader, Seq[(Path, Long)]]

    for ((terminal, group)<- sortedGroups.items()){
      val (newResults, newEvaluated) = evaluateGroupCached(
        terminal,
        group,
        results,
        classLoaderSignatureCache
      )
      for(ev <- newEvaluated){
        evaluated.append(ev)
      }
      for((k, v) <- newResults) results.put(k, v)

    }

    val failing = new util.MultiBiMap.Mutable[Either[Task[_], LabelledTarget[_]], Result.Failing]
    for((k, vs) <- sortedGroups.items()){
      failing.addAll(k, vs.items.flatMap(results.get).collect{case f: Result.Failing => f})
    }
    Evaluator.Results(goals.indexed.map(results), evaluated, transitive, failing)
  }

  def resolveDestPaths(t: LabelledTarget[_]): (Path, Path) = {
    val segmentStrings = t.segments.flatMap{
      case Mirror.Segment.Label(s) => Seq(s)
      case Mirror.Segment.Cross(values) => values.map(_.toString)
    }
    val targetDestPath = workspacePath / segmentStrings
    val metadataPath = targetDestPath / up / (targetDestPath.last + ".mill.json")
    (targetDestPath, metadataPath)
  }

  def evaluateGroupCached(terminal: Either[Task[_], LabelledTarget[_]],
                          group: OSet[Task[_]],
                          results: collection.Map[Task[_], Result[Any]],
                          signatureCache: mutable.Map[ClassLoader, Seq[(Path, Long)]]): (collection.Map[Task[_], Result[Any]], Seq[Task[_]]) = {


    val externalInputs = group.items.flatMap(_.inputs).filter(!group.contains(_))

    // check if the build itself has changed
    val classLoaderSig = group.toVector.map(_.getClass.getClassLoader).map { cl =>
      signatureCache.getOrElseUpdate(cl, cl match {
        case scl: SpecialClassLoader => scl.classpathSignature
        case ucl: URLClassLoader => SpecialClassLoader.initialClasspathSignature(ucl)
        case _ => Nil
      })
    }

    val inputsHash =
      externalInputs.map(results).toVector.hashCode +
      group.toIterator.map(_.sideHash).toVector.hashCode() +
      classLoaderSig.hashCode()

    terminal match{
      case Left(task) =>
        evaluateGroup(group, results, targetDestPath = None, maybeTargetLabel = None)
      case Right(labelledTarget) =>
        val (destPath, metadataPath) = resolveDestPaths(labelledTarget)
        val cached = for{
          json <- scala.util.Try(upickle.json.read(read(metadataPath))).toOption
          (cachedHash, terminalResult) <- scala.util.Try(upickle.default.readJs[(Int, upickle.Js.Value)](json)).toOption
          if cachedHash == inputsHash
        } yield terminalResult

        cached match{
          case Some(terminalResult) =>
            val newResults = mutable.LinkedHashMap.empty[Task[_], Result[Any]]
            newResults(labelledTarget.target) = labelledTarget.format.read(terminalResult)
            (newResults, Nil)

          case _ =>

            val Seq(first, rest @_*) = labelledTarget.segments
            val msgParts = Seq(first.asInstanceOf[Mirror.Segment.Label].value) ++ rest.map{
              case Mirror.Segment.Label(s) => "." + s
              case Mirror.Segment.Cross(s) => "[" + s.mkString(",") + "]"
            }

            if (labelledTarget.target.flushDest) rm(destPath)
            val (newResults, newEvaluated) = evaluateGroup(
              group,
              results,
              Some(destPath),
              maybeTargetLabel = Some(msgParts.mkString))

            newResults(labelledTarget.target) match{
              case Result.Success(v) =>
                val terminalResult = labelledTarget
                  .format
                  .asInstanceOf[upickle.default.ReadWriter[Any]]
                  .write(v)

                write.over(metadataPath, upickle.default.write(inputsHash -> terminalResult, indent = 4))
              case Result.Skipped =>
                // Do nothing
              case _: Result.Failing =>
                // Wipe out any cached metadata.mill.json file that exists, so
                // a following run won't look at the cached metadata file and
                // assume it's associated with the possibly-borked state of the
                // destPath after an evaluation failure.
                rm(metadataPath)
            }



            (newResults, newEvaluated)
        }
    }
  }


  def evaluateGroup(group: OSet[Task[_]],
                    results: collection.Map[Task[_], Result[Any]],
                    targetDestPath: Option[Path],
                    maybeTargetLabel: Option[String]) = {


    val newEvaluated = mutable.Buffer.empty[Task[_]]
    val newResults = mutable.LinkedHashMap.empty[Task[_], Result[Any]]

    val nonEvaluatedTargets = group.indexed.filterNot(results.contains)

    maybeTargetLabel.foreach { targetLabel =>
      val inputResults = for {
        target <- nonEvaluatedTargets
        item <- target.inputs.filterNot(group.contains)
      } yield results(item)

      val logRun = inputResults.forall(_.isInstanceOf[Result.Success[_]])

      if(logRun) { log.info("Running " + targetLabel) }
    }

    val multiLogger = resolveLogger(targetDestPath)

    for (target <- nonEvaluatedTargets) {

      newEvaluated.append(target)
      val targetInputValues = target.inputs
        .map(x => newResults.getOrElse(x, results(x)))
        .collect{ case Result.Success(v) => v }

      val res =
        if (targetInputValues.length != target.inputs.length) Result.Skipped
        else {
          val args = new Ctx(
            targetInputValues.toArray[Any],
            targetDestPath.orNull,
            multiLogger
          )

          val out = System.out
          val err = System.err
          try{
            System.setErr(multiLogger.outputStream)
            System.setOut(multiLogger.outputStream)
            Console.withOut(multiLogger.outputStream){
              Console.withErr(multiLogger.outputStream){
                target.evaluate(args)
              }
            }
          }finally{
            System.setErr(err)
            System.setOut(out)
          }
        }

      newResults(target) = res
    }

    multiLogger.close()

    (newResults, newEvaluated)
  }

  def resolveLogger(targetDestPath: Option[Path]): Logger = {
    if (targetDestPath.isEmpty && sel.isEmpty)
      log
    else {
      val path = targetDestPath.getOrElse(
        sel.foldLeft[Path](pwd / 'out) {
          case (d, Label(s)) => d / s
          case (d, Cross(args)) => d / args.map(_.toString)
        }
      )
      val dir = path / up
      mkdir(dir)
      val file = dir / (path.last + ".log")
      rm(file)
      MultiLogger(log, FileLogger(file))
    }
  }

}


object Evaluator{

  case class Results(rawValues: Seq[Result[Any]],
                     evaluated: OSet[Task[_]],
                     transitive: OSet[Task[_]],
                     failing: MultiBiMap[Either[Task[_], LabelledTarget[_]], Result.Failing]){
    def values = rawValues.collect{case Result.Success(v) => v}
  }
}