/* 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.File // Node needs to be renamed to XNode because the task subproject contains a Node type that will shadow // scala.xml.Node when generating aggregated API documentation import scala.xml.{Elem, Node => XNode, NodeSeq, PrettyPrinter} import Configurations.Optional import org.apache.ivy.{core, plugins, Ivy} import core.settings.IvySettings import core.module.descriptor.{DependencyArtifactDescriptor, DependencyDescriptor, License, ModuleDescriptor, ExcludeRule} import plugins.resolver.{ChainResolver, DependencyResolver, IBiblioResolver} class MakePom(val log: Logger) { @deprecated("Use `write(Ivy, ModuleDescriptor, ModuleInfo, Option[Iterable[Configuration]], Set[String], NodeSeq, XNode => XNode, MavenRepository => Boolean, Boolean, File)` instead", "0.11.2") 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(ivy, module, moduleInfo: ModuleInfo, configurations: Option[Iterable[Configuration]], Set(Artifact.DefaultType), extra, process, filterRepositories, allRepositories, output) def write(ivy: Ivy, module: ModuleDescriptor, moduleInfo: ModuleInfo, configurations: Option[Iterable[Configuration]], includeTypes: Set[String], extra: NodeSeq, process: XNode => XNode, filterRepositories: MavenRepository => Boolean, allRepositories: Boolean, output: File): Unit = write(process(toPom(ivy, module, moduleInfo, configurations, includeTypes, 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) { IO.write(output, "" + newline + xmlString) } def toString(node: XNode): String = new PrettyPrinter(1000, 4).format(node) @deprecated("Use `toPom(Ivy, ModuleDescriptor, ModuleInfo, Option[Iterable[Configuration]], Set[String], NodeSeq, MavenRepository => Boolean, Boolean)` instead", "0.11.2") def toPom(ivy: Ivy, module: ModuleDescriptor, moduleInfo: ModuleInfo, configurations: Option[Iterable[Configuration]], extra: NodeSeq, filterRepositories: MavenRepository => Boolean, allRepositories: Boolean): XNode = toPom(ivy, module, moduleInfo, configurations, Set(Artifact.DefaultType), extra, filterRepositories, allRepositories) def toPom(ivy: Ivy, module: ModuleDescriptor, moduleInfo: ModuleInfo, configurations: Option[Iterable[Configuration]], includeTypes: Set[String], extra: NodeSeq, filterRepositories: MavenRepository => Boolean, allRepositories: Boolean): XNode = ( 4.0.0 { makeModuleID(module) } {moduleInfo.nameFormal} { makeStartYear(moduleInfo) } { makeOrganization(moduleInfo) } { makeScmInfo(moduleInfo) } { extra } { val deps = depsInConfs(module, configurations) makeProperties(module, deps) ++ makeDependencies(deps, includeTypes) } { 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 match { case Some(y) => {y} case _ => NodeSeq.Empty } def makeOrganization(moduleInfo: ModuleInfo): NodeSeq = { {moduleInfo.organizationName} { moduleInfo.organizationHomepage match { case Some(h) => {h} case _ => NodeSeq.Empty }} } def makeScmInfo(moduleInfo: ModuleInfo): NodeSeq = { moduleInfo.scmInfo match { case Some(s) => {s.browseUrl} {s.connection} {s.devConnection match { case Some(d) => {d} case _ => NodeSeq.Empty }} case _ => NodeSeq.Empty } } def makeProperties(module: ModuleDescriptor, dependencies: Seq[DependencyDescriptor]): NodeSeq = { val extra = IvySbt.getExtraAttributes(module) val depExtra = CustomPomParser.writeDependencyExtra(dependencies).mkString("\n") val allExtra = if(depExtra.isEmpty) extra else extra.updated(CustomPomParser.ExtraAttributesKey, depExtra) if(allExtra.isEmpty) NodeSeq.Empty else makeProperties(allExtra) } 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(dependencies: Seq[DependencyDescriptor], includeTypes: Set[String]): NodeSeq = if(dependencies.isEmpty) NodeSeq.Empty else { dependencies.map(makeDependency(_, includeTypes)) } def makeDependency(dependency: DependencyDescriptor, includeTypes: Set[String]): NodeSeq = { val artifacts = dependency.getAllDependencyArtifacts val includeArtifacts = artifacts.filter(d => includeTypes(d.getType)) if(artifacts.isEmpty) { val (scope, optional) = getScopeAndOptional(dependency.getModuleConfigurations) makeDependencyElem(dependency, scope, optional, None, None) } else if(includeArtifacts.isEmpty) NodeSeq.Empty else NodeSeq.fromSeq(artifacts.map( a => makeDependencyElem(dependency, a) )) } def makeDependencyElem(dependency: DependencyDescriptor, artifact: DependencyArtifactDescriptor): Elem = { val artifactConfigs = artifact.getConfigurations val configs = if(artifactConfigs.isEmpty) dependency.getModuleConfigurations else artifactConfigs val (scope, optional) = getScopeAndOptional(configs) makeDependencyElem(dependency, scope, optional, artifactClassifier(artifact), artifactType(artifact)) } def makeDependencyElem(dependency: DependencyDescriptor, scope: Option[String], optional: Boolean, classifier: Option[String], tpe: Option[String]): Elem = { val mrid = dependency.getDependencyRevisionId {mrid.getOrganisation} {mrid.getName} {mrid.getRevision} { scopeElem(scope) } { optionalElem(optional) } { classifierElem(classifier) } { typeElem(tpe) } { exclusions(dependency) } } @deprecated("No longer used and will be removed.", "0.12.1") def classifier(dependency: DependencyDescriptor, includeTypes: Set[String]): NodeSeq = { val jarDep = dependency.getAllDependencyArtifacts.filter(d => includeTypes(d.getType)).headOption jarDep match { case Some(a) => classifierElem(artifactClassifier(a)) case None => NodeSeq.Empty } } def artifactType(artifact: DependencyArtifactDescriptor): Option[String] = Option(artifact.getType).flatMap { tpe => if(tpe == "jar") None else Some(tpe) } def typeElem(tpe: Option[String]): NodeSeq = tpe match { case Some(t) => {t} case None => NodeSeq.Empty } def artifactClassifier(artifact: DependencyArtifactDescriptor): Option[String] = Option(artifact.getExtraAttribute("classifier")) def classifierElem(classifier: Option[String]): NodeSeq = classifier match { case Some(c) => {c} case None => NodeSeq.Empty } @deprecated("No longer used and will be removed.", "0.12.1") def scopeAndOptional(dependency: DependencyDescriptor): NodeSeq = { val (scope, opt) = getScopeAndOptional(dependency.getModuleConfigurations) scopeElem(scope) ++ optionalElem(opt) } def scopeElem(scope: Option[String]): NodeSeq = scope match { case None | Some(Configurations.Compile.name) => NodeSeq.Empty case Some(s) => {s} } 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 exclusions(dependency: DependencyDescriptor): NodeSeq = { val excl = dependency.getExcludeRules(dependency.getModuleConfigurations) val (warns, excls) = List.separate(excl.map(makeExclusion)) if(!warns.isEmpty) log.warn(warns.mkString(IO.Newline)) if(!excls.isEmpty) {excls} else NodeSeq.Empty } 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 } }