package cbt import cbt._ import java.io._ import java.nio.file._ import scala.xml._ trait ExportBuildInformation { self: BaseBuild => def buildInfoXml: String = BuildInformationSerializer.serialize(BuildInformation.Project(self)).toString } object BuildInformation { case class Project( name: String, root: File, rootModule: Module, modules: Seq[Module], libraries: Seq[Library], cbtLibraries: Seq[Library], scalaCompilers: Seq[ScalaCompiler] ) case class Module( name: String, root: File, scalaVersion: String, sourceDirs: Seq[File], target: File, binaryDependencies: Seq[BinaryDependency], moduleDependencies: Seq[ModuleDependency], classpath: Seq[File], parentBuild: Option[String], scalacOptions: Seq[String] ) case class Library( name: String, jars: Seq[LibraryJar] ) case class BinaryDependency( name: String ) case class ModuleDependency( name: String ) case class ScalaCompiler( version: String, jars: Seq[File] ) case class LibraryJar( jar: File, jarType: JarType.JarType ) object JarType extends Enumeration { type JarType = Value val Binary = Value("binary") val Source = Value("source") } object Project { def apply(build: BaseBuild): Project = new BuildInformationExporter(build).exportBuildInformation class BuildInformationExporter(rootBuild: BaseBuild) { def exportBuildInformation: Project = { val moduleBuilds = transitiveBuilds(rootBuild) val libraries = moduleBuilds .flatMap(_.transitiveDependencies) .collect { case d: BoundMavenDependency => exportLibrary(d) } .distinct val cbtLibraries = convertCbtLibraries val rootModule = exportModule(rootBuild) val modules = moduleBuilds .map(exportModule) .distinct val scalaCompilers = modules .map(_.scalaVersion) .map(v => ScalaCompiler(v, resolveScalaCompiler(rootBuild, v))) Project( rootModule.name, rootModule.root, rootModule, modules, libraries, cbtLibraries, scalaCompilers ) } private def convertCbtLibraries = transitiveBuilds(DirectoryDependency(rootBuild.context.cbtHome)(rootBuild.context).dependenciesArray.head.asInstanceOf[BaseBuild]) .collect { case d: BoundMavenDependency => d.jar case d: PackageJars => d.jar.get } .map(exportLibrary) .distinct private def collectDependencies(dependencies: Seq[Dependency]): Seq[ModuleDependency] = dependencies .collect { case d: BaseBuild => Seq(ModuleDependency(moduleName(d))) case d: LazyDependency => collectDependencies(Seq(d.dependency)) } .flatten private def exportModule(build: BaseBuild): Module = { val moduleDependencies = collectDependencies(build.dependencies) val mavenDependencies = build.dependencies .collect { case d: BoundMavenDependency => BinaryDependency(formatMavenDependency(d.mavenDependency)) } val classpath = build.dependencyClasspath.files .filter(_.isFile) .distinct val sourceDirs = { val s = build.sources .filter(_.exists) .map(handleSource) if (s.nonEmpty) commonParents(s) else Seq(build.projectDirectory) } Module( name = moduleName(build), root = build.projectDirectory, scalaVersion = build.scalaVersion, sourceDirs = sourceDirs, target = build.target, binaryDependencies = mavenDependencies, moduleDependencies = moduleDependencies, classpath = classpath, parentBuild = build.context.parentBuild.map(b => moduleName(b.asInstanceOf[BaseBuild])), scalacOptions = build.scalacOptions ) } private def commonParents(paths: Seq[File]): Seq[File] = { //Too slow O(n^2) val buffer = scala.collection.mutable.ListBuffer.empty[Path] val sorted = paths .map(_.toPath.toAbsolutePath) .sortWith(_.getNameCount < _.getNameCount) for (x <- sorted) { if (!buffer.exists(x.startsWith)) { buffer += x } } buffer .toList .map(_.toFile) } private def collectLazyBuilds(dependency: Dependency): Option[BaseBuild] = dependency match { case l: LazyDependency => l.dependency match { case d: BaseBuild => Some(d) case d: LazyDependency => collectLazyBuilds(d.dependency) case _ => None } case d: BaseBuild => Some(d) case _ => None } private def transitiveBuilds(build: BaseBuild): Seq[BaseBuild] = (build +: build.transitiveDependencies) .collect { case d: BaseBuild => d +: collectParentBuilds(d).flatMap(transitiveBuilds) case d: LazyDependency => collectLazyBuilds(d.dependency) .toSeq .flatMap(transitiveBuilds) } .flatten .distinct private def exportLibrary(dependency: BoundMavenDependency) = { val name = formatMavenDependency(dependency.mavenDependency) val jars = (dependency +: dependency.transitiveDependencies) .map(_.asInstanceOf[BoundMavenDependency]) val binaryJars = jars .map(_.jar) .map(LibraryJar(_, JarType.Binary)) implicit val logger: Logger = rootBuild.context.logger implicit val transientCache: java.util.Map[AnyRef, AnyRef] = rootBuild.context.transientCache implicit val classLoaderCache: ClassLoaderCache = rootBuild.context.classLoaderCache val sourceJars = jars .map { d => d.copy(mavenDependency = d.mavenDependency.copy(classifier = Classifier.sources)).jar } .map(LibraryJar(_, JarType.Source)) Library(name, binaryJars ++ sourceJars) } private def exportLibrary(file: File) = Library("CBT:" + file.getName.stripSuffix(".jar"), Seq(LibraryJar(file, JarType.Binary))) private def collectParentBuilds(build: BaseBuild): Seq[BaseBuild] = build.context.parentBuild .map(_.asInstanceOf[BaseBuild]) .map(b => b +: collectParentBuilds(b)) .toSeq .flatten private def resolveScalaCompiler(build: BaseBuild, scalaVersion: String) = build.Resolver(mavenCentral, sonatypeReleases).bindOne( MavenDependency("org.scala-lang", "scala-compiler", scalaVersion) ).classpath.files private def handleSource(source: File): File = if (source.isDirectory) source else source.getParentFile //Let's asume that for now private def formatMavenDependency(dependency: cbt.MavenDependency) = s"${dependency.groupId}:${dependency.artifactId}:${dependency.version}" private def moduleName(build: BaseBuild) = if (rootBuild.projectDirectory == build.projectDirectory) rootBuild.projectDirectory.getName else build.projectDirectory.getPath .drop(rootBuild.projectDirectory.getPath.length) .stripPrefix("/") .replace("/", "-") } } } object BuildInformationSerializer { def serialize(project: BuildInformation.Project): Node = {project.modules.map(serialize)} {project.libraries.map(serialize)} {project.cbtLibraries.map(serialize)} {project.scalaCompilers.map(serialize)} private def serialize(module: BuildInformation.Module): Node = {module.sourceDirs.map(d => {d})} {module.scalacOptions.map(o => )} {module.binaryDependencies.map(serialize)} {module.moduleDependencies.map(serialize)} {module.classpath.map(c => {c.toString})} {module.parentBuild.map(p => {p}).getOrElse(NodeSeq.Empty)} private def serialize(binaryDependency: BuildInformation.BinaryDependency): Node = {binaryDependency.name} private def serialize(library: BuildInformation.Library): Node = {library.jars.map(j => {j.jar})} private def serialize(compiler: BuildInformation.ScalaCompiler): Node = {compiler.jars.map(j => {j})} private def serialize(moduleDependency: BuildInformation.ModuleDependency): Node = {moduleDependency.name} }