/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
// based on Ivy's PomModuleDescriptorWriter, which is Apache Licensed, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package sbt;
import java.io.{BufferedWriter, File, OutputStreamWriter, FileOutputStream}
import scala.xml.{Node => XNode, NodeSeq, PrettyPrinter, XML}
import Configurations.Optional
import org.apache.ivy.{core, plugins, Ivy}
import core.settings.IvySettings
import core.module.{descriptor, id}
import descriptor.{DependencyDescriptor, License, ModuleDescriptor, ExcludeRule}
import id.ModuleRevisionId
import plugins.resolver.{ChainResolver, DependencyResolver, IBiblioResolver}
class MakePom(val log: Logger)
{
def encoding = "UTF-8"
def write(ivy: Ivy, module: ModuleDescriptor, moduleInfo: ModuleInfo, configurations: Option[Iterable[Configuration]], extra: NodeSeq, process: XNode => XNode, filterRepositories: MavenRepository => Boolean, allRepositories: Boolean, output: File): Unit =
write(process(toPom(ivy, module, moduleInfo, configurations, extra, filterRepositories, allRepositories)), output)
// use \n as newline because toString uses PrettyPrinter, which hard codes line endings to be \n
def write(node: XNode, output: File): Unit = write(toString(node), output, "\n")
def write(xmlString: String, output: File, newline: String)
{
output.getParentFile.mkdirs()
val out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(output), encoding))
try
{
out.write("" + newline)
out.write(xmlString)
}
finally { out.close() }
}
def toString(node: XNode): String = new PrettyPrinter(1000, 4).format(node)
def toPom(ivy: Ivy, module: ModuleDescriptor, moduleInfo: ModuleInfo, configurations: Option[Iterable[Configuration]], extra: NodeSeq, filterRepositories: MavenRepository => Boolean, allRepositories: Boolean): XNode =
(
4.0.0
{ makeModuleID(module) }
{moduleInfo.nameFormal}
{ makeStartYear(moduleInfo) }
{ makeOrganization(moduleInfo) }
{ extra }
{ makeProperties(module) }
{ makeDependencies(module, configurations) }
{ makeRepositories(ivy.getSettings, allRepositories, filterRepositories) }
)
def makeModuleID(module: ModuleDescriptor): NodeSeq =
{
val mrid = moduleDescriptor(module)
val a: NodeSeq =
({ mrid.getOrganisation }
{ mrid.getName }
{ packaging(module) })
val b: NodeSeq =
( (description(module.getDescription) ++
homePage(module.getHomePage) ++
revision(mrid.getRevision) ++
licenses(module.getLicenses)) : NodeSeq )
a ++ b
}
def makeStartYear(moduleInfo: ModuleInfo): NodeSeq = moduleInfo.startYear map { y => {y} } getOrElse NodeSeq.Empty
def makeOrganization(moduleInfo: ModuleInfo): NodeSeq =
{
{moduleInfo.organizationName}
{ moduleInfo.organizationHomepage map { h => {h} } getOrElse NodeSeq.Empty }
}
def makeProperties(module: ModuleDescriptor): NodeSeq =
{
val extra = IvySbt.getExtraAttributes(module)
if(extra.isEmpty) NodeSeq.Empty else makeProperties(extra)
}
def makeProperties(extra: Map[String,String]): NodeSeq =
{
for( (key,value) <- extra ) yield
({value}).copy(label = key)
}
def description(d: String) = if((d eq null) || d.isEmpty) NodeSeq.Empty else {d}
def licenses(ls: Array[License]) = if(ls == null || ls.isEmpty) NodeSeq.Empty else {ls.map(license)}
def license(l: License) =
{l.getName}
{l.getUrl}
repo
def homePage(homePage: String) = if(homePage eq null) NodeSeq.Empty else {homePage}
def revision(version: String) = if(version ne null) {version} else NodeSeq.Empty
def packaging(module: ModuleDescriptor) =
module.getAllArtifacts match
{
case Array() => "pom"
case Array(x) => x.getType
case xs =>
val types = xs.map(_.getType).toList.filterNot(IgnoreTypes)
types match {
case Nil => Artifact.PomType
case xs if xs.contains(Artifact.DefaultType) => Artifact.DefaultType
case x :: xs => x
}
}
val IgnoreTypes: Set[String] = Set(Artifact.SourceType, Artifact.DocType, Artifact.PomType)
def makeDependencies(module: ModuleDescriptor, configurations: Option[Iterable[Configuration]]): NodeSeq =
{
val dependencies = depsInConfs(module, configurations)
if(dependencies.isEmpty) NodeSeq.Empty
else
{ dependencies.map(makeDependency) }
}
def makeDependency(dependency: DependencyDescriptor): NodeSeq =
{
val mrid = dependency.getDependencyRevisionId
val excl = dependency.getExcludeRules(dependency.getModuleConfigurations)
{mrid.getOrganisation}
{mrid.getName}
{mrid.getRevision}
{ scopeAndOptional(dependency) }
{
val (warns, excls) = List.separate(excl.map(makeExclusion))
if(!warns.isEmpty) log.warn(warns.mkString(IO.Newline))
if(excls.isEmpty) NodeSeq.Empty
else
{ excls }
}
}
def scopeAndOptional(dependency: DependencyDescriptor): NodeSeq =
{
val (scope, opt) = getScopeAndOptional(dependency.getModuleConfigurations)
scopeElem(scope) ++ optionalElem(opt)
}
def scopeElem(scope: Option[String]): NodeSeq = scope match {
case Some(s) => {s}
case None => NodeSeq.Empty
}
def optionalElem(opt: Boolean) = if(opt) true else NodeSeq.Empty
def moduleDescriptor(module: ModuleDescriptor) = module.getModuleRevisionId
def getScopeAndOptional(confs: Array[String]): (Option[String], Boolean) =
{
val (opt, notOptional) = confs.partition(_ == Optional.name)
val defaultNotOptional = Configurations.defaultMavenConfigurations.find(notOptional contains _.name)
val scope = defaultNotOptional match
{
case Some(conf) => Some(conf.name)
case None =>
if(notOptional.isEmpty || notOptional(0) == Configurations.Default.name)
None
else
Option(notOptional(0))
}
(scope, !opt.isEmpty)
}
def makeExclusion(exclRule: ExcludeRule): Either[String, NodeSeq] =
{
val m = exclRule.getId.getModuleId
val (g, a) = (m.getOrganisation, m.getName)
if(g == null || g.isEmpty || g == "*" || a.isEmpty || a == "*")
Left("Skipped generating '' for %s. Dependency exclusion should have both 'org' and 'module' to comply with Maven POM's schema.".format(m))
else
Right(
{g}
{a}
)
}
def makeRepositories(settings: IvySettings, includeAll: Boolean, filterRepositories: MavenRepository => Boolean) =
{
class MavenRepo(name: String, snapshots: Boolean, releases: Boolean)
val repositories = if(includeAll) allResolvers(settings) else resolvers(settings.getDefaultResolver)
val mavenRepositories =
repositories.flatMap {
case m: IBiblioResolver if m.isM2compatible && m.getRoot != IBiblioResolver.DEFAULT_M2_ROOT =>
MavenRepository(m.getName, m.getRoot) :: Nil
case _ => Nil
}
val repositoryElements = mavenRepositories.filter(filterRepositories).map(mavenRepository)
if(repositoryElements.isEmpty) repositoryElements else {repositoryElements}
}
def allResolvers(settings: IvySettings): Seq[DependencyResolver] = flatten(castResolvers(settings.getResolvers)).distinct
def flatten(rs: Seq[DependencyResolver]): Seq[DependencyResolver] = if(rs eq null) Nil else rs.flatMap(resolvers)
def resolvers(r: DependencyResolver): Seq[DependencyResolver] =
r match { case c: ChainResolver => flatten(castResolvers(c.getResolvers)); case _ => r :: Nil }
// cast the contents of a pre-generics collection
private def castResolvers(s: java.util.Collection[_]): Seq[DependencyResolver] =
s.toArray.map(_.asInstanceOf[DependencyResolver])
def toID(name: String) = checkID(name.filter(isValidIDCharacter).mkString, name)
def isValidIDCharacter(c: Char) = c.isLetterOrDigit
private def checkID(id: String, name: String) = if(id.isEmpty) error("Could not convert '" + name + "' to an ID") else id
def mavenRepository(repo: MavenRepository): XNode =
mavenRepository(toID(repo.name), repo.name, repo.root)
def mavenRepository(id: String, name: String, root: String): XNode =
{id}
{name}
{root}
{ if(name == JavaNet1Repository.name) "legacy" else "default" }
/** Retain dependencies only with the configurations given, or all public configurations of `module` if `configurations` is None.
* This currently only preserves the information required by makePom*/
private def depsInConfs(module: ModuleDescriptor, configurations: Option[Iterable[Configuration]]): Seq[DependencyDescriptor] =
{
val keepConfigurations = IvySbt.getConfigurations(module, configurations)
val keepSet = Set(keepConfigurations.toSeq : _*)
def translate(dependency: DependencyDescriptor) =
{
val keep = dependency.getModuleConfigurations.filter(keepSet.contains)
if(keep.isEmpty)
None
else // TODO: translate the dependency to contain only configurations to keep
Some(dependency)
}
module.getDependencies flatMap translate
}
}