aboutsummaryrefslogblamecommitdiff
path: root/stage1/Stage1Lib.scala
blob: ac77a9211821b0167fa3bd848f80569ebdde21d4 (plain) (tree)
1
2
3
4
5
6




                  
                                                  








                                                          

















                                                                        




















































































                                                                                                                      



                                                                                             

   
                                                                                      
                                                                          





                                                                                








                                                                 
       












                                                                                                                        
                                 




















                                                                                                                





















                                                                      



       
                                            








                               




























                                                                                

 
package cbt

import cbt.paths._

import java.io._
import java.lang.reflect.InvocationTargetException
import java.net._
import java.nio.file._
import javax.tools._
import java.security._
import java.util._
import javax.xml.bind.annotation.adapters.HexBinaryAdapter

import scala.collection.immutable.Seq

// CLI interop
case class ExitCode(code: Int)
object ExitCode{
  val Success = ExitCode(0)
  val Failure = ExitCode(1)
}

class TrappedExitCode(private val exitCode: Int) extends Exception
object TrappedExitCode{
  def unapply(e: Throwable): Option[ExitCode] =
    Option(e) flatMap {
      case i: InvocationTargetException => unapply(i.getTargetException)
      case e: TrappedExitCode => Some( ExitCode(e.exitCode) )
      case _ => None
    }
}


case class Context( cwd: String, args: Seq[String], logger: Logger )

case class ClassPath(files: Seq[File]){
  private val duplicates = (files diff files.distinct).distinct
  assert(
    duplicates.isEmpty,
    "Duplicate classpath entries found:\n" + duplicates.mkString("\n") + "\nin classpath:\n"+string
  )
  private val nonExisting = files.distinct.filterNot(_.exists)
  assert(
    duplicates.isEmpty,
    "Classpath contains entires that don't exist on disk:\n" + nonExisting.mkString("\n") + "\nin classpath:\n"+string
  )
  
  def +:(file: File) = ClassPath(file +: files)
  def :+(file: File) = ClassPath(files :+ file)
  def ++(other: ClassPath) = ClassPath(files ++ other.files)
  def string = strings.mkString( File.pathSeparator )
  def strings = files.map{
    f => f.toString + ( if(f.isDirectory) "/" else "" )
  }
  def toConsole = string
}
object ClassPath{
  def flatten( classPaths: Seq[ClassPath] ): ClassPath = ClassPath( classPaths.map(_.files).flatten )
}

class Stage1Lib( val logger: Logger ){
  lib =>

  // ========== reflection ==========

  /** Create instance of the given class via reflection */
  def create(cls: String)(args: Any*)(classLoader: ClassLoader): Any = {
    logger.composition( logger.showInvocation("Stage1Lib.create", (classLoader,cls,args)) )
    import scala.reflect.runtime.universe._
    val m = runtimeMirror(classLoader)
    val sym = m.classSymbol(classLoader.loadClass(cls))
    val cm = m.reflectClass( sym.asClass )
    val tpe = sym.toType
    val ctorm = cm.reflectConstructor( tpe.decl(termNames.CONSTRUCTOR).asMethod )
    ctorm(args:_*)
  }

  // ========== file system / net ==========
  
  def array2hex(padTo: Int, array: Array[Byte]): String = {
    val hex = new java.math.BigInteger(1, array).toString(16)
    ("0" * (padTo-hex.size)) + hex
  }
  def md5( bytes: Array[Byte] ): String = array2hex(32, MessageDigest.getInstance("MD5").digest(bytes))
  def sha1( bytes: Array[Byte] ): String = array2hex(40, MessageDigest.getInstance("SHA-1").digest(bytes))

  def red(string: String) = scala.Console.RED+string+scala.Console.RESET
  def blue(string: String) = scala.Console.BLUE+string+scala.Console.RESET
  def green(string: String) = scala.Console.GREEN+string+scala.Console.RESET

  def download(urlString: URL, target: Path, sha1: Option[String]){
    val incomplete = Paths.get(target+".incomplete");
    if( !Files.exists(target) ){
      new File(target.toString).getParentFile.mkdirs
      logger.resolver(blue("downloading ")+urlString)
      logger.resolver(blue("to ")+target)
      val stream = urlString.openStream
      Files.copy(stream, incomplete, StandardCopyOption.REPLACE_EXISTING)
      sha1.foreach{
        hash =>
          val expected = hash
          val actual = this.sha1(Files.readAllBytes(incomplete))
          assert( expected == actual, s"$expected == $actual" )
          logger.resolver(green("verified")+" checksum for "+target)
      }
      stream.close
      Files.move(incomplete, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
    }
  }
 
  def listFilesRecursive(f: File): Seq[File] = {
    f +: (
      if( f.isDirectory ) f.listFiles.flatMap(listFilesRecursive).toVector else Seq[File]()
    )
  }

  // ========== compilation / execution ==========

  def runMainIfFound(cls: String, args: Seq[String], classLoader: ClassLoader ): ExitCode = {
    if( classLoader.canLoad(cls) ){
      runMain(cls, args, classLoader )
    } else ExitCode.Success
  }

  def runMain(cls: String, args: Seq[String], classLoader: ClassLoader ): ExitCode = {
    logger.lib(s"Running $cls.main($args) with classLoader: "+classLoader)
    trapExitCode{
      classLoader
        .loadClass(cls)
        .getMethod( "main", scala.reflect.classTag[Array[String]].runtimeClass )
        .invoke( null, args.toArray.asInstanceOf[AnyRef] )
    }
  }

  implicit class ClassLoaderExtensions(classLoader: ClassLoader){
    def canLoad(className: String) = {
      try{
        classLoader.loadClass(className)
        true
      } catch {
        case e: ClassNotFoundException => false
      }
    }
  }

  def zinc(
    needsRecompile: Boolean, files: Seq[File], compileTarget: File, classpath: ClassPath, extraArgs: Seq[String] = Seq()
  )( zincVersion: String, scalaVersion: String ): Unit = {

    val cp = classpath.string
    if(classpath.files.isEmpty) throw new Exception("Trying to compile with empty classpath. Source files: "+files)
    if(files.isEmpty) throw new Exception("Trying to compile no files. ClassPath: "+cp)

    // only run zinc if files changed, for performance reasons
    // FIXME: this is broken, need invalidate on changes in dependencies as well
    if( true || needsRecompile ){
      val zinc = MavenDependency("com.typesafe.zinc","zinc", zincVersion)(logger)
      val zincDeps = zinc.transitiveDependencies

      val sbtInterface =
        zincDeps
          .collect{ case d @ MavenDependency( "com.typesafe.sbt", "sbt-interface", _, false ) => d }
          .headOption
          .getOrElse( throw new Exception(s"cannot find sbt-interface in zinc $zincVersion dependencies") )
          .jar

      val compilerInterface =
        zincDeps
          .collect{ case d @ MavenDependency( "com.typesafe.sbt", "compiler-interface", _, true ) => d }
          .headOption
          .getOrElse( throw new Exception(s"cannot find compiler-interface in zinc $zincVersion dependencies") )
          .jar

      val scalaLibrary = MavenDependency("org.scala-lang","scala-library",scalaVersion)(logger).jar
      val scalaReflect = MavenDependency("org.scala-lang","scala-reflect",scalaVersion)(logger).jar
      val scalaCompiler = MavenDependency("org.scala-lang","scala-compiler",scalaVersion)(logger).jar

      val code = redirectOutToErr{
        trapExitCode{
          lib.runMain(
            "com.typesafe.zinc.Main",
            Seq(
              "-scala-compiler", scalaCompiler.toString,
              "-scala-library", scalaLibrary.toString,
              "-sbt-interface", sbtInterface.toString,
              "-compiler-interface", compilerInterface.toString,
              "-scala-extra", scalaReflect.toString,
              "-cp", cp,
              "-d", compileTarget.toString
            ) ++ extraArgs.map("-S"+_) ++ files.map(_.toString),
            zinc.classLoader
          )
        }
      }
      if(code != ExitCode.Success){
        // FIXME: zinc currently always returns exit code 0
        // hack that triggers recompilation next time. Nicer solution?
        val now = System.currentTimeMillis()
        files.foreach{_.setLastModified(now)}
      }
    }

  }
  def redirectOutToErr[T](code: => T): T = {
    val oldOut = System.out
    try{
      System.setOut(System.err)
      code
    } finally{
      System.setOut(oldOut)
    }
  }

  def trapExitCode( code: => Unit ): ExitCode = {
    val old: Option[SecurityManager] = Option(System.getSecurityManager())
    try{
      val securityManager = new SecurityManager{
        override def checkPermission( permission: Permission ) = {
          /*
          NOTE: is it actually ok, to just make these empty?
          Calling .super leads to ClassNotFound exteption for a lambda.
          Calling to the previous SecurityManager leads to a stack overflow
          */
        }
        override def checkPermission( permission: Permission, context: Any ) = {
          /* Does this methods need to be overidden? */
        }
        override def checkExit( status: Int ) = {
          super.checkExit(status)
          logger.lib(s"checkExit($status)")
          throw new TrappedExitCode(status)
        }
      }
      System.setSecurityManager( securityManager )
      code
      ExitCode.Success
    } catch {
      case TrappedExitCode(exitCode) => exitCode
    } finally {
      System.setSecurityManager(old.getOrElse(null))
    }
  }
}