diff options
author | Christopher Vogt <oss.nsp@cvogt.org> | 2016-02-06 13:03:36 -0500 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2016-03-04 15:06:30 -0500 |
commit | 974942db43ff2d1fa7ba71ad60f9bb9eae2d8631 (patch) | |
tree | d7235df9d4d6a67753dc2a20ab6bfcb7a24dc74c /stage1/resolver.scala | |
download | cbt-974942db43ff2d1fa7ba71ad60f9bb9eae2d8631.tar.gz cbt-974942db43ff2d1fa7ba71ad60f9bb9eae2d8631.tar.bz2 cbt-974942db43ff2d1fa7ba71ad60f9bb9eae2d8631.zip |
CBT Version 1.0-BETA
Diffstat (limited to 'stage1/resolver.scala')
-rw-r--r-- | stage1/resolver.scala | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/stage1/resolver.scala b/stage1/resolver.scala new file mode 100644 index 0000000..880289c --- /dev/null +++ b/stage1/resolver.scala @@ -0,0 +1,264 @@ +package cbt +import java.nio.file._ +import java.net._ +import java.io._ +import scala.collection.immutable.Seq +import scala.xml._ +import paths._ + +private final class Tree( val root: Dependency, computeChildren: => Seq[Tree] ){ + lazy val children = computeChildren + def linearize: Seq[Dependency] = root +: children.flatMap(_.linearize) + def show(indent: Int = 0): Stream[Char] = { + (" " * indent + root.show + "\n").toStream #::: children.map(_.show(indent+1)).foldLeft(Stream.empty[Char])(_ #::: _) + } +} + +trait ArtifactInfo extends Dependency{ + def artifactId: String + def groupId: String + def version: String + + protected def str = s"$groupId:$artifactId:$version" + override def show = super.show + s"($str)" +} +abstract class Dependency{ + + def updated: Boolean + //def cacheClassLoader: Boolean = false + def exportedClasspath: ClassPath + def exportedJars: Seq[File] + def jars: Seq[File] = exportedJars ++ dependencyJars + + def cacheDependencyClassLoader = true + + private object cacheClassLoaderBasicBuild extends Cache[URLClassLoader] + def classLoader: URLClassLoader = cacheClassLoaderBasicBuild{ + val transitiveClassPath = transitiveDependencies.map{ + case d: MavenDependency => Left(d) + case d => Right(d) + } + val buildClassPath = ClassPath.flatten( + transitiveClassPath.flatMap( + _.right.toOption.map(_.exportedClasspath) + ) + ) + val mavenClassPath = ClassPath.flatten( + transitiveClassPath.flatMap( + _.left.toOption + ).par.map(_.exportedClasspath).seq.sortBy(_.string) + ) + if(cacheDependencyClassLoader){ + val mavenClassPathKey = mavenClassPath.strings.sorted.mkString(":") + new URLClassLoader( + exportedClasspath ++ buildClassPath, + ClassLoaderCache.classLoader( + mavenClassPathKey, new URLClassLoader( mavenClassPath, ClassLoader.getSystemClassLoader ) + ) + ) + } else { + new URLClassLoader( + exportedClasspath ++ buildClassPath ++ mavenClassPath, ClassLoader.getSystemClassLoader + ) + } + } + def classpath : ClassPath = exportedClasspath ++ dependencyClasspath + def dependencyJars : Seq[File] = transitiveDependencies.flatMap(_.jars) + def dependencyClasspath : ClassPath = ClassPath.flatten( transitiveDependencies.map(_.exportedClasspath) ) + def dependencies: Seq[Dependency] + + private def resolveRecursive(parents: List[Dependency] = List()): Tree = { + // diff removes circular dependencies + new Tree(this, (dependencies diff parents).map(_.resolveRecursive(this :: parents))) + } + + def transitiveDependencies: Seq[Dependency] = { + val deps = dependencies.flatMap(_.resolveRecursive().linearize) + val hasInfo = deps.collect{ case d:ArtifactInfo => d } + val noInfo = deps.filter{ + case _:ArtifactInfo => false + case _ => true + } + noInfo ++ MavenDependency.removeOutdated( hasInfo ) + } + + def show: String = this.getClass.getSimpleName + // ========== debug ========== + def dependencyTree: String = dependencyTreeRecursion() + def logger: Logger + protected def lib = new Stage1Lib(logger) + private def dependencyTreeRecursion(indent: Int = 0): String = ( " " * indent ) + (if(updated) lib.red(show) else show) + dependencies.map(_.dependencyTreeRecursion(indent + 1)).map("\n"+_).mkString("") + + private object cacheDependencyClassLoaderBasicBuild extends Cache[ClassLoader] +} + +// TODO: all this hard codes the scala version, needs more flexibility +class ScalaCompiler(logger: Logger) extends MavenDependency("org.scala-lang","scala-compiler",constants.scalaVersion)(logger) +class ScalaLibrary(logger: Logger) extends MavenDependency("org.scala-lang","scala-library",constants.scalaVersion)(logger) +class ScalaReflect(logger: Logger) extends MavenDependency("org.scala-lang","scala-reflect",constants.scalaVersion)(logger) + +case class ScalaDependencies(logger: Logger) extends Dependency{ + def exportedClasspath = ClassPath(Seq()) + def exportedJars = Seq[File]() + def dependencies = Seq( new ScalaCompiler(logger), new ScalaLibrary(logger), new ScalaReflect(logger) ) + final val updated = false +} + +/* +case class BinaryDependency( path: File, dependencies: Seq[Dependency] ) extends Dependency{ + def exportedClasspath = ClassPath(Seq(path)) + def exportedJars = Seq[File]() +} +*/ + +case class Stage1Dependency(logger: Logger) extends Dependency{ + def exportedClasspath = ClassPath( Seq(nailgunTarget, stage1Target) ) + def exportedJars = Seq[File]() + def dependencies = ScalaDependencies(logger: Logger).dependencies + def updated = false // FIXME: think this through, might allow simplifications and/or optimizations +} +case class CbtDependency(logger: Logger) extends Dependency{ + def exportedClasspath = ClassPath( Seq( stage2Target ) ) + def exportedJars = Seq[File]() + override def dependencies = Seq( + Stage1Dependency(logger), + MavenDependency("net.incongru.watchservice","barbary-watchservice","1.0")(logger), + MavenDependency("com.lihaoyi","ammonite-repl_2.11.7","0.5.5")(logger), + MavenDependency("org.scala-lang.modules","scala-xml_2.11","1.0.5")(logger) + ) + def updated = false // FIXME: think this through, might allow simplifications and/or optimizations +} + +sealed trait ClassifierBase +final case class Classifier(name: String) extends ClassifierBase +case object javadoc extends ClassifierBase +case object sources extends ClassifierBase + +case class MavenDependency( groupId: String, artifactId: String, version: String, sources: Boolean = false )(val logger: Logger) + extends ArtifactInfo{ + + def updated = false + + private val groupPath = groupId.split("\\.").mkString("/") + def basePath = s"/$groupPath/$artifactId/$version/$artifactId-$version"+(if(sources) "-sources" else "") + + private def resolverUrl = if(version.endsWith("-SNAPSHOT")) "https://oss.sonatype.org/content/repositories/snapshots" else "https://repo1.maven.org/maven2" + private def baseUrl = resolverUrl + basePath + private def baseFile = mavenCache + basePath + private def pomFile = baseFile+".pom" + private def jarFile = baseFile+".jar" + //private def coursierJarFile = userHome+"/.coursier/cache/v1/https/repo1.maven.org/maven2"+basePath+".jar" + private def pomUrl = baseUrl+".pom" + private def jarUrl = baseUrl+".jar" + + def exportedJars = Seq( jar ) + def exportedClasspath = ClassPath( exportedJars ) + + import scala.collection.JavaConversions._ + + def jarSha1 = { + val file = jarFile+".sha1" + def url = jarUrl+".sha1" + scala.util.Try{ + lib.download( new URL(url), Paths.get(file), None ) + // split(" ") here so checksum file contents in this format work: df7f15de037a1ee4d57d2ed779739089f560338c jna-3.2.2.pom + Files.readAllLines(Paths.get(file)).mkString("\n").split(" ").head.trim + }.toOption // FIXME: .toOption is a temporary solution to ignore if libs don't have one + } + def pomSha1 = { + val file = pomFile+".sha1" + def url = pomUrl+".sha1" + scala.util.Try{ + lib.download( new URL(url), Paths.get(file), None ) + // split(" ") here so checksum file contents in this format work: df7f15de037a1ee4d57d2ed779739089f560338c jna-3.2.2.pom + Files.readAllLines(Paths.get(file)).mkString("\n").split(" ").head.trim + }.toOption // FIXME: .toOption is a temporary solution to ignore if libs don't have one + } + def jar = { + lib.download( new URL(jarUrl), Paths.get(jarFile), jarSha1 ) + new File(jarFile) + } + def pomXml = { + XML.loadFile(pom.toString) + } + def pom = { + lib.download( new URL(pomUrl), Paths.get(pomFile), pomSha1 ) + new File(pomFile) + } + + // ========== pom traversal ========== + + lazy val pomParents: Seq[MavenDependency] = { + (pomXml \ "parent").collect{ + case parent => + MavenDependency( + (parent \ "groupId").text, + (parent \ "artifactId").text, + (parent \ "version").text + )(logger) + } + } + def dependencies: Seq[MavenDependency] = { + if(sources) Seq() + else (pomXml \ "dependencies" \ "dependency").collect{ + case xml if (xml \ "scope").text == "" && (xml \ "optional").text != "true" => + MavenDependency( + lookup(xml,_ \ "groupId").get, + lookup(xml,_ \ "artifactId").get, + lookup(xml,_ \ "version").get, + (xml \ "classifier").text == "sources" + )(logger) + }.toVector + } + def lookup( xml: Node, accessor: Node => NodeSeq ): Option[String] = { + //println("lookup in "+pomUrl) + val Substitution = "\\$\\{([a-z0-9\\.]+)\\}".r + accessor(xml).headOption.flatMap{v => + //println("found: "+v.text) + v.text match { + case Substitution(path) => + //println("lookup "+path + ": "+(pomXml\path).text) + lookup(pomXml, _ \ "properties" \ path) + case value => Option(value) + } + }.orElse( + pomParents.map(p => p.lookup(p.pomXml, accessor)).flatten.headOption + ) + } +} +object MavenDependency{ + def semanticVersionLessThan(left: String, right: String) = { + // FIXME: this ignores ends when different size + val zipped = left.split("\\.|\\-").map(toInt) zip right.split("\\.|\\-").map(toInt) + val res = zipped.map { + case (Left(i),Left(j)) => i compare j + case (Right(i),Right(j)) => i compare j + case (Left(i),Right(j)) => i.toString compare j + case (Right(i),Left(j)) => i compare j.toString + } + res.find(_ != 0).map(_ < 0).getOrElse(false) + } + def toInt(str: String): Either[Int,String] = try { + Left(str.toInt) + } catch { + case e: NumberFormatException => Right(str) + } + /* this obviously should be overridable somehow */ + def removeOutdated( + deps: Seq[ArtifactInfo], + versionLessThan: (String, String) => Boolean = semanticVersionLessThan + ): Seq[ArtifactInfo] = { + val latest = deps + .groupBy( d => (d.groupId, d.artifactId) ) + .mapValues( + _.sortBy( _.version )( Ordering.fromLessThan(versionLessThan) ) + .last + ) + deps.flatMap{ + d => + val l = latest.get((d.groupId,d.artifactId)) + //if(d != l) println("EVICTED: "+d.show) + l + }.distinct + } +} |