diff --git a/ivy/Ivy.scala b/ivy/Ivy.scala index bd1adc399..d24691df9 100644 --- a/ivy/Ivy.scala +++ b/ivy/Ivy.scala @@ -14,7 +14,7 @@ import org.apache.ivy.{core, plugins, util, Ivy} import core.IvyPatternHelper import core.cache.{CacheMetadataOptions, DefaultRepositoryCacheManager} import core.module.descriptor.{Artifact => IArtifact, DefaultArtifact, DefaultDependencyArtifactDescriptor, MDArtifact} -import core.module.descriptor.{DefaultDependencyDescriptor, DefaultModuleDescriptor, DependencyDescriptor, ModuleDescriptor} +import core.module.descriptor.{DefaultDependencyDescriptor, DefaultModuleDescriptor, DependencyDescriptor, ModuleDescriptor, License} import core.module.id.{ArtifactId,ModuleId, ModuleRevisionId} import core.resolve.IvyNode import core.settings.IvySettings @@ -26,7 +26,7 @@ import plugins.resolver.{ChainResolver, DependencyResolver} import util.{Message, MessageLogger} import util.extendable.ExtendableItem -import scala.xml.NodeSeq +import scala.xml.{NodeSeq, Text} final class IvySbt(val configuration: IvyConfiguration) { @@ -124,9 +124,9 @@ final class IvySbt(val configuration: IvyConfiguration) moduleSettings match { case ic: InlineConfiguration => configureInline(ic, configuration.log) - case ec: EmptyConfiguration => configureEmpty(ec.module) - case pc: PomConfiguration => readPom(pc.file, pc.validate) - case ifc: IvyFileConfiguration => readIvyFile(ifc.file, ifc.validate) + case ec: EmptyConfiguration => configureEmpty(ec) + case pc: PomConfiguration => configurePom(pc) + case ifc: IvyFileConfiguration => configureIvyFile(ifc) } moduleSettings.ivyScala.foreach(IvyScala.checkModule(baseModule, baseConfiguration)) baseModule.getExtraAttributesNamespaces.asInstanceOf[java.util.Map[String,String]].put("e", "http://ant.apache.org/ivy/extra") @@ -135,7 +135,7 @@ final class IvySbt(val configuration: IvyConfiguration) private def configureInline(ic: InlineConfiguration, log: Logger) = { import ic._ - val moduleID = newConfiguredModuleID(module, configurations) + val moduleID = newConfiguredModuleID(module, moduleInfo, configurations) val defaultConf = defaultConfiguration getOrElse Configurations.config(ModuleDescriptor.DEFAULT_CONFIGURATION) log.debug("Using inline dependencies specified in Scala" + (if(ivyXML.isEmpty) "." else " and XML.")) @@ -144,44 +144,43 @@ final class IvySbt(val configuration: IvyConfiguration) IvySbt.addMainArtifact(moduleID) (moduleID, parser.getDefaultConf) } - private def newConfiguredModuleID(module: ModuleID, configurations: Iterable[Configuration]) = + private def newConfiguredModuleID(module: ModuleID, moduleInfo: ModuleInfo, configurations: Iterable[Configuration]) = { val mod = new DefaultModuleDescriptor(IvySbt.toID(module), "release", null, false) mod.setLastModified(System.currentTimeMillis) + mod.setDescription(moduleInfo.description) + moduleInfo.homepage foreach { h => mod.setHomePage(h.toString) } + moduleInfo.licenses foreach { l => mod.addLicense(new License(l._1, l._2.toString)) } IvySbt.addConfigurations(mod, configurations) IvySbt.addArtifacts(mod, module.explicitArtifacts) mod } - /** Parses the given Maven pom 'pomFile'.*/ - private def readPom(pomFile: File, validate: Boolean) = + /** Parses the Maven pom 'pomFile' from the given `PomConfiguration`.*/ + private def configurePom(pc: PomConfiguration) = { - val md = PomModuleDescriptorParser.getInstance.parseDescriptor(settings, toURL(pomFile), validate) + val md = PomModuleDescriptorParser.getInstance.parseDescriptor(settings, toURL(pc.file), pc.validate) val dmd = IvySbt.toDefaultModuleDescriptor(md) IvySbt.addConfigurations(dmd, Configurations.defaultInternal) (dmd, "compile") } - /** Parses the given Ivy file 'ivyFile'.*/ - private def readIvyFile(ivyFile: File, validate: Boolean) = + /** Parses the Ivy file 'ivyFile' from the given `IvyFileConfiguration`.*/ + private def configureIvyFile(ifc: IvyFileConfiguration) = { - val url = toURL(ivyFile) val parser = new CustomXmlParser.CustomParser(settings, None) - parser.setValidate(validate) - parser.setSource(url) + parser.setValidate(ifc.validate) + parser.setSource(toURL(ifc.file)) parser.parse() val md = parser.getModuleDescriptor() (IvySbt.toDefaultModuleDescriptor(md), parser.getDefaultConf) } private def toURL(file: File) = file.toURI.toURL - private def configureEmpty(module: ModuleID) = + private def configureEmpty(ec: EmptyConfiguration) = { val defaultConf = ModuleDescriptor.DEFAULT_CONFIGURATION - val moduleID = new DefaultModuleDescriptor(IvySbt.toID(module), "release", null, false) - moduleID.setLastModified(System.currentTimeMillis) - moduleID.addConfiguration(IvySbt.toIvyConfiguration(Configurations.Default)) - IvySbt.addArtifacts(moduleID, module.explicitArtifacts) - IvySbt.addMainArtifact(moduleID) - (moduleID, defaultConf) + val mod = newConfiguredModuleID(ec.module, ec.moduleInfo, Seq(Configurations.Default)) + IvySbt.addMainArtifact(mod) + (mod, defaultConf) } } } @@ -348,12 +347,11 @@ private object IvySbt /** Creates a full ivy file for 'module' using the 'dependencies' XML as the part after the <info>...</info> section. */ private def wrapped(module: ModuleID, dependencies: NodeSeq) = { - import module._ { if(hasInfo(module, dependencies)) NodeSeq.Empty else - addExtraAttributes(, module.extraAttributes) + addExtraAttributes(defaultInfo(module), module.extraAttributes) } {dependencies} { @@ -362,6 +360,10 @@ private object IvySbt } } + private[this] def defaultInfo(module: ModuleID): scala.xml.Elem = { + import module._ + + } private[this] def addExtraAttributes(elem: scala.xml.Elem, extra: Map[String, String]): scala.xml.Elem = (elem /: extra) { case (e, (key,value) ) => e % new scala.xml.UnprefixedAttribute(key, value, scala.xml.Null) } private def hasInfo(module: ModuleID, x: scala.xml.NodeSeq) = diff --git a/ivy/IvyActions.scala b/ivy/IvyActions.scala index 45944d5a1..a1c26b020 100644 --- a/ivy/IvyActions.scala +++ b/ivy/IvyActions.scala @@ -26,7 +26,7 @@ final class PublishConfiguration(val ivyFile: Option[File], val resolverName: St final class UpdateConfiguration(val retrieve: Option[RetrieveConfiguration], val missingOk: Boolean, val logging: UpdateLogging.Value) final class RetrieveConfiguration(val retrieveDirectory: File, val outputPattern: String) -final case class MakePomConfiguration(file: File, configurations: Option[Iterable[Configuration]] = None, extra: NodeSeq = NodeSeq.Empty, process: XNode => XNode = n => n, filterRepositories: MavenRepository => Boolean = _ => true, allRepositories: Boolean) +final case class MakePomConfiguration(file: File, moduleInfo: ModuleInfo, configurations: Option[Iterable[Configuration]] = None, extra: NodeSeq = NodeSeq.Empty, process: XNode => XNode = n => n, filterRepositories: MavenRepository => Boolean = _ => true, allRepositories: Boolean) // exclude is a map on a restricted ModuleID final case class GetClassifiersConfiguration(module: GetClassifiersModule, exclude: Map[ModuleID, Set[String]], configuration: UpdateConfiguration, ivyScala: Option[IvyScala]) final case class GetClassifiersModule(id: ModuleID, modules: Seq[ModuleID], configurations: Seq[Configuration], classifiers: Seq[String]) @@ -66,9 +66,9 @@ object IvyActions /** Creates a Maven pom from the given Ivy configuration*/ def makePom(module: IvySbt#Module, configuration: MakePomConfiguration, log: Logger) { - import configuration.{allRepositories, configurations, extra, file, filterRepositories, process} + import configuration.{allRepositories, moduleInfo, configurations, extra, file, filterRepositories, process} module.withModule(log) { (ivy, md, default) => - (new MakePom).write(ivy, md, configurations, extra, process, filterRepositories, allRepositories, file) + (new MakePom).write(ivy, md, moduleInfo, configurations, extra, process, filterRepositories, allRepositories, file) log.info("Wrote " + file.getAbsolutePath) } } @@ -170,7 +170,7 @@ object IvyActions import config.{configuration => c, ivyScala, module => mod} import mod.{configurations => confs, id, modules => deps} val base = restrictedCopy(id).copy(name = id.name + "$" + label) - val module = new ivySbt.Module(InlineConfiguration(base, deps).copy(ivyScala = ivyScala)) + val module = new ivySbt.Module(InlineConfiguration(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala)) val report = update(module, c, log) val newConfig = config.copy(module = mod.copy(modules = report.allModules)) updateClassifiers(ivySbt, newConfig, log) @@ -183,7 +183,7 @@ object IvyActions val baseModules = modules map restrictedCopy val deps = baseModules.distinct flatMap classifiedArtifacts(classifiers, exclude) val base = restrictedCopy(id).copy(name = id.name + classifiers.mkString("$","_","")) - val module = new ivySbt.Module(InlineConfiguration(base, deps).copy(ivyScala = ivyScala, configurations = confs)) + val module = new ivySbt.Module(InlineConfiguration(base, ModuleInfo(base.name), deps).copy(ivyScala = ivyScala, configurations = confs)) val upConf = new UpdateConfiguration(c.retrieve, true, c.logging) update(module, upConf, log) } diff --git a/ivy/IvyConfigurations.scala b/ivy/IvyConfigurations.scala index b151054bd..09fa96fcb 100644 --- a/ivy/IvyConfigurations.scala +++ b/ivy/IvyConfigurations.scala @@ -62,21 +62,17 @@ final case class PomConfiguration(file: File, ivyScala: Option[IvyScala], valida { def noScala = copy(ivyScala = None) } -final case class InlineConfiguration(module: ModuleID, dependencies: Seq[ModuleID], ivyXML: NodeSeq, - configurations: Seq[Configuration], defaultConfiguration: Option[Configuration], ivyScala: Option[IvyScala], - validate: Boolean) extends ModuleSettings +final case class InlineConfiguration(module: ModuleID, moduleInfo: ModuleInfo, dependencies: Seq[ModuleID], ivyXML: NodeSeq = NodeSeq.Empty, configurations: Seq[Configuration] = Nil, defaultConfiguration: Option[Configuration] = None, ivyScala: Option[IvyScala] = None, validate: Boolean = false) extends ModuleSettings { def withConfigurations(configurations: Seq[Configuration]) = copy(configurations = configurations) def noScala = copy(ivyScala = None) } -final case class EmptyConfiguration(module: ModuleID, ivyScala: Option[IvyScala], validate: Boolean) extends ModuleSettings +final case class EmptyConfiguration(module: ModuleID, moduleInfo: ModuleInfo, ivyScala: Option[IvyScala], validate: Boolean) extends ModuleSettings { def noScala = copy(ivyScala = None) } object InlineConfiguration { - def apply(module: ModuleID, dependencies: Seq[ModuleID]) = - new InlineConfiguration(module, dependencies, NodeSeq.Empty, Nil, None, None, false) def configurations(explicitConfigurations: Iterable[Configuration], defaultConfiguration: Option[Configuration]) = if(explicitConfigurations.isEmpty) { @@ -92,7 +88,7 @@ object InlineConfiguration } object ModuleSettings { - def apply(ivyScala: Option[IvyScala], validate: Boolean, module: => ModuleID)(baseDirectory: File, log: Logger) = + def apply(ivyScala: Option[IvyScala], validate: Boolean, module: => ModuleID, moduleInfo: => ModuleInfo)(baseDirectory: File, log: Logger) = { log.debug("Autodetecting dependencies.") val defaultPOMFile = IvySbt.defaultPOM(baseDirectory) @@ -106,8 +102,8 @@ object ModuleSettings else { log.warn("No dependency configuration found, using defaults.") - new EmptyConfiguration(module, ivyScala, validate) + new EmptyConfiguration(module, moduleInfo, ivyScala, validate) } } } -} \ No newline at end of file +} diff --git a/ivy/IvyInterface.scala b/ivy/IvyInterface.scala index 3631bbef0..a1eadeb1c 100644 --- a/ivy/IvyInterface.scala +++ b/ivy/IvyInterface.scala @@ -38,6 +38,14 @@ object ModuleID for ( (key, value) <- attributes) yield if(key.startsWith("e:")) (key, value) else ("e:" + key, value) } +/** Additional information about a project module */ +case class ModuleInfo(nameFormal: String, description: String = "", homepage: Option[URL] = None, licenses: Seq[(String, URL)] = Nil, organizationName: String = "", organizationHomepage: Option[URL] = None) +{ + def formally(name: String) = copy(nameFormal = name) + def describing(desc: String, home: Option[URL]) = copy(description = desc, homepage = home) + def licensed(lics: (String, URL)*) = copy(licenses = lics) + def organization(name: String, home: Option[URL]) = copy(organizationName = name, organizationHomepage = home) +} sealed trait Resolver { def name: String diff --git a/ivy/MakePom.scala b/ivy/MakePom.scala index 361520698..939ee92bb 100644 --- a/ivy/MakePom.scala +++ b/ivy/MakePom.scala @@ -20,8 +20,8 @@ import plugins.resolver.{ChainResolver, DependencyResolver, IBiblioResolver} class MakePom { def encoding = "UTF-8" - def write(ivy: Ivy, module: ModuleDescriptor, configurations: Option[Iterable[Configuration]], extra: NodeSeq, process: XNode => XNode, filterRepositories: MavenRepository => Boolean, allRepositories: Boolean, output: File): Unit = - write(process(toPom(ivy, module, configurations, extra, filterRepositories, allRepositories)), output) + 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) @@ -37,10 +37,12 @@ class MakePom } def toString(node: XNode): String = new PrettyPrinter(1000, 4).format(node) - def toPom(ivy: Ivy, module: ModuleDescriptor, configurations: Option[Iterable[Configuration]], extra: NodeSeq, filterRepositories: MavenRepository => Boolean, allRepositories: Boolean): XNode = + 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} + { makeOrganization(moduleInfo) } { extra } { makeProperties(module) } { makeDependencies(module, configurations) } @@ -61,6 +63,13 @@ class MakePom licenses(module.getLicenses)) : NodeSeq ) a ++ b } + 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) diff --git a/main/Defaults.scala b/main/Defaults.scala index 1d6ccb027..fd16f6f0b 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -595,7 +595,13 @@ object Classpaths conflictWarning <<= (thisProjectRef, conflictWarning) { (ref, cw) => cw.copy(label = Project.display(ref)) }, unmanagedBase <<= baseDirectory / "lib", normalizedName <<= name(StringUtilities.normalize), + description <<= description or name.identity, + homepage in GlobalScope :== None, + licenses in GlobalScope :== Nil, organization <<= organization or normalizedName.identity, + organizationName in GlobalScope <<= organizationName or organization.identity, + organizationHomepage in GlobalScope <<= organizationHomepage or homepage.identity, + projectInfo <<= (name, description, homepage, licenses, organizationName, organizationHomepage) apply ModuleInfo, classpathFilter in GlobalScope :== "*.jar", externalResolvers <<= (externalResolvers.task.? zipWith resolvers.identity) { case (Some(delegated), Seq()) => delegated @@ -645,8 +651,8 @@ object Classpaths }, ivyConfigurations ++= Configurations.auxiliary, moduleSettings <<= moduleSettings0, - makePomConfiguration <<= (artifactPath in makePom, pomExtra, pomPostProcess, pomIncludeRepository, pomAllRepositories) { - (file, extra, process, include, all) => new MakePomConfiguration(file, None, extra, process, include, all) + makePomConfiguration <<= (artifactPath in makePom, projectInfo, pomExtra, pomPostProcess, pomIncludeRepository, pomAllRepositories) { + (file, minfo, extra, process, include, all) => new MakePomConfiguration(file, minfo, None, extra, process, include, all) }, deliverLocalConfiguration <<= (crossTarget, ivyLoggingLevel) map { (outDir, level) => deliverConfig( outDir, logging = level ) }, deliverConfiguration <<= deliverLocalConfiguration.identity, @@ -686,8 +692,8 @@ object Classpaths new IvySbt(conf) } def moduleSettings0: Initialize[Task[ModuleSettings]] = - (projectID, allDependencies, ivyXML, ivyConfigurations, defaultConfiguration, ivyScala, ivyValidate) map { - (pid, deps, ivyXML, confs, defaultConf, ivyS, validate) => new InlineConfiguration(pid, deps, ivyXML, confs, defaultConf, ivyS, validate) + (projectID, allDependencies, ivyXML, ivyConfigurations, defaultConfiguration, ivyScala, ivyValidate, projectInfo) map { + (pid, deps, ivyXML, confs, defaultConf, ivyS, validate, pinfo) => new InlineConfiguration(pid, pinfo, deps, ivyXML, confs, defaultConf, ivyS, validate) } def sbtClassifiersTasks = inTask(updateSbtClassifiers)(Seq( diff --git a/main/Keys.scala b/main/Keys.scala index 2b4b8430d..a4bf865a6 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -4,6 +4,7 @@ package sbt import java.io.File + import java.net.URL import Project.ScopedKey import complete._ import inc.Analysis @@ -171,9 +172,15 @@ object Keys // Classpath/Dependency Management Keys type Classpath = Seq[Attributed[File]] - val name = SettingKey[String]("name", "Name.") - val normalizedName = SettingKey[String]("normalized-name", "Name transformed from mixed case and spaces to lowercase and dash-separated.") + val name = SettingKey[String]("name", "Project name.") + val normalizedName = SettingKey[String]("normalized-name", "Project name transformed from mixed case and spaces to lowercase and dash-separated.") + val description = SettingKey[String]("description", "Project description.") + val homepage = SettingKey[Option[URL]]("homepage", "Project homepage.") + val licenses = SettingKey[Seq[(String, URL)]]("licenses", "Project licenses as (name, url) pairs.") val organization = SettingKey[String]("organization", "Organization/group ID.") + val organizationName = SettingKey[String]("organization-name", "Organization full/formal name.") + val organizationHomepage = SettingKey[Option[URL]]("organization-homepage", "Organization homepage.") + val projectInfo = SettingKey[ModuleInfo]("project-info", "Addition project information like formal name, homepage, licenses etc.") val defaultConfiguration = SettingKey[Option[Configuration]]("default-configuration", "Defines the configuration used when none is specified for a dependency.") val defaultConfigurationMapping = SettingKey[String]("default-configuration-mapping", "Defines the mapping used for a simple, unmapped configuration definition.")