sbt/main/ClasspathProject.scala

226 lines
7.8 KiB
Scala

/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import std._
import inc.Analysis
import TaskExtra._
import ClasspathProject._
import java.io.File
import Path._
import Types._
import scala.xml.NodeSeq
trait ClasspathProject
{
def configurations: Seq[Configuration]
val products: Classpath
val unmanagedClasspath: Classpath
val managedClasspath: Classpath
val internalDependencyClasspath: Classpath
lazy val externalDependencyClasspath: Classpath =
TaskMap { (configuration: Configuration) =>
val un = unmanagedClasspath(configuration)
val m = managedClasspath(configuration)
(un, m) map concat[Attributed[File]]
}
lazy val dependencyClasspath: Classpath =
TaskMap { (configuration: Configuration) =>
val external = externalDependencyClasspath(configuration)
val internal = internalDependencyClasspath(configuration)
(external, internal) map concat[Attributed[File]]
}
lazy val fullClasspath: Classpath =
TaskMap { case (configuration: Configuration) =>
val dep = dependencyClasspath(configuration)
val prod = products(configuration)
(dep, prod) map concat[Attributed[File]]
}
}
trait BasicClasspathProject extends ClasspathProject
{
val ivyConfiguration: Task[IvyConfiguration]
val moduleSettings: Task[ModuleSettings]
val unmanagedBase: Task[File]
lazy val updateConfig: Task[UpdateConfiguration] = task {
new UpdateConfiguration(null, null, true, UpdateLogging.Full)
}
lazy val ivySbt: Task[IvySbt] =
ivyConfiguration map { conf => new IvySbt(conf) }
lazy val ivyModule: Task[IvySbt#Module] =
(ivySbt, moduleSettings) map { (ivySbt: IvySbt, settings: ModuleSettings) =>
new ivySbt.Module(settings)
}
def classpathFilter: FileFilter = GlobFilter("*.jar")
def defaultExcludeFilter: FileFilter = MultiProject.defaultExcludes
override val managedClasspath: Classpath =
TaskMap { configuration =>
update map { x => attributed(x.getOrElse(configuration, error("No such configuration '" + configuration.toString + "'")) ) }
}
val unmanagedClasspath: Classpath =
TaskMap { configuration =>
unmanagedBase map { base =>
attributed( (base * (classpathFilter -- defaultExcludeFilter) +++
(base / configuration.toString).descendentsExcept(classpathFilter, defaultExcludeFilter)).getFiles.toSeq )
}
}
lazy val configurationMap: Map[String, Configuration] =
configurations map { conf => (conf.name, conf) } toMap;
import Types._
lazy val update = (ivyModule, updateConfig) map { case module :+: config :+: HNil =>
val confMap = configurationMap
IvyActions.update(module, config) map { case (key, value) => (confMap(key), value) } toMap;
}
}
trait DefaultClasspathProject extends BasicClasspathProject with Project
{
def projectID: ModuleID
def baseResolvers: Seq[Resolver]
lazy val resolvers: Task[Seq[Resolver]] = task { baseResolvers }
def otherResolvers: Seq[Resolver] = Nil
def moduleConfigurations: Seq[ModuleConfiguration] = Nil
def offline: Boolean = false
def paths: IvyPaths = new IvyPaths(info.projectDirectory, None)
lazy val ivyConfiguration: Task[IvyConfiguration] = resolvers map { rs =>
// TODO: log should be passed directly to IvyActions and pulled from Streams
new InlineIvyConfiguration(paths, rs, otherResolvers, moduleConfigurations, offline, Some(info.globalLock), ConsoleLogger())
}
def libraryDependencies: Iterable[ModuleID] = ReflectUtilities.allVals[ModuleID](this).map(_._2)
def dependencyPath: Path = info.projectDirectory / "lib"
def ivyXML: NodeSeq = NodeSeq.Empty
def defaultConfiguration: Option[Configuration] = None
def ivyScala: Option[IvyScala] = None
def ivyValidate: Boolean = false
//TODO: transitive dependencies
lazy val internalDependencyClasspath: Classpath =
TaskMap { (conf: Configuration) =>
val confMap = configurationMap
val productsTasks =
for( (p: ClasspathProject, Some(confString)) <- ClasspathProject.resolvedDependencies(this)) yield
{
println("Project " + p.name + ", conf: " + confString)
val to = parseSimpleConfigurations(confString).getOrElse(conf.toString, missingMapping(this.name, p.name, conf.toString))
p.products(confMap(to))
}
(productsTasks.toSeq.join) named(name + "/join") map(_.flatten) named(name + "/int")
}
lazy val unmanagedBase = task { dependencyPath.asFile }
lazy val moduleSettings: Task[ModuleSettings] = task {
new InlineConfiguration(projectID, libraryDependencies, ivyXML, configurations, defaultConfiguration, ivyScala, ivyValidate)
}
}
trait MultiClasspathProject extends DefaultClasspathProject
{
def dependencies: Iterable[ProjectDependency.Classpath]
def name: String
def organization: String
def version: String
def projectDependencies: Iterable[ModuleID] =
resolvedDependencies(this) collect { case (p: DefaultClasspathProject, conf) => p.projectID.copy(configurations = conf) }
lazy val projectResolver =
depMap(this) map { m =>
new RawRepository(new ProjectResolver("inter-project", m))
}
override def projectID = ModuleID(organization, name, version)
override def libraryDependencies: Iterable[ModuleID] = super.libraryDependencies ++ projectDependencies
override lazy val resolvers: Task[Seq[Resolver]] = projectResolver map { _ +: baseResolvers }
}
trait ReflectiveClasspathProject extends DefaultClasspathProject
{
private[this] def vals[T: Manifest] = ReflectUtilities.allVals[T](this).toSeq.map(_._2)
def configurations: Seq[Configuration] = vals[Configuration]
def baseResolvers: Seq[Resolver] = Resolver.withDefaultResolvers(vals[Resolver] )
}
import org.apache.ivy.core.module
import module.id.ModuleRevisionId
import module.descriptor.ModuleDescriptor
object ClasspathProject
{
type Classpath = Configuration => Task[Seq[Attributed[File]]]
val Analyzed = AttributeKey[Analysis]("analysis")
def attributed[T](in: Seq[T]): Seq[Attributed[T]] = in map Attributed.blank
def analyzed[T](data: T, analysis: Analysis) = Attributed.blank(data).put(Analyzed, analysis)
def analyzed(compile: Task[Analysis], inputs: Task[Compile.Inputs]): Task[Attributed[File]] =
(compile, inputs) map { case analysis :+: i :+: HNil =>
analyzed(i.config.classesDirectory, analysis)
}
def concat[A]: (Seq[A], Seq[A]) => Seq[A] = _ ++ _
def extractAnalysis[T](a: Attributed[T]): (T, Analysis) =
(a.data, a.metadata get Analyzed getOrElse Analysis.Empty)
def analysisMap[T](cp: Seq[Attributed[T]]): Map[T, Analysis] =
(cp map extractAnalysis).toMap
def data[T](in: Seq[Attributed[T]]): Seq[T] = in.map(_.data)
def depMap(root: Project): Task[Map[ModuleRevisionId, ModuleDescriptor]] =
depMap(MultiProject.topologicalSort(root).dropRight(1) collect { case cp: DefaultClasspathProject => cp })
def depMap(projects: Seq[DefaultClasspathProject]): Task[Map[ModuleRevisionId, ModuleDescriptor]] =
projects.map( _.ivyModule ).join.map { mods =>
(mods.map{ mod =>
val md = mod.moduleDescriptor
(md.getModuleRevisionId, md)
}).toMap
}
def resolvedDependencies(p: Project): Iterable[(Project, Option[String])] =
p.dependencies map { cp =>
(resolveProject(cp.project, p), cp.configuration)
}
def resolveProject(e: Either[File, Project], context: Project): Project =
e match {
case Left(extPath) => context.info.externals(extPath)
case Right(proj) => proj
}
def parseSimpleConfigurations(confString: String): Map[String, String] =
confString.split(";").flatMap( conf =>
trim(conf.split("->",2)) match {
case x :: Nil => (x,x) :: Nil
case x :: y :: Nil => trim(x.split(",")) map { a => (a,y) }
case _ => error("Invalid configuration '" + conf + "'") // shouldn't get here
}
).toMap
private def trim(a: Array[String]): List[String] = a.toList.map(_.trim)
def missingMapping(from: String, to: String, conf: String) =
error("No configuration mapping defined from '" + from + "' to '" + to + "' for '" + conf + "'")
}