diff --git a/core/shared/src/main/scala/coursier/core/Definitions.scala b/core/shared/src/main/scala/coursier/core/Definitions.scala index 089e0f490..3d85be538 100644 --- a/core/shared/src/main/scala/coursier/core/Definitions.scala +++ b/core/shared/src/main/scala/coursier/core/Definitions.scala @@ -76,7 +76,10 @@ case class Project( // Ivy-specific // First String is configuration - publications: Seq[(String, Publication)] + publications: Seq[(String, Publication)], + + // Extra infos, not used during resolution + info: Info ) { def moduleVersion = (module, version) @@ -85,6 +88,25 @@ case class Project( Orders.allConfigurations(configurations) } +/** Extra project info, not used during resolution */ +case class Info( + description: String, + homePage: String, + licenses: Seq[(String, Option[String])], + developers: Seq[Info.Developer], + publication: Option[Versions.DateTime] +) + +object Info { + case class Developer( + id: String, + name: String, + url: String + ) + + val empty = Info("", "", Nil, Nil, None) +} + // Maven-specific case class Activation(properties: Seq[(String, Option[String])]) diff --git a/core/shared/src/main/scala/coursier/ivy/IvyXml.scala b/core/shared/src/main/scala/coursier/ivy/IvyXml.scala index 7725817bc..467c8dd48 100644 --- a/core/shared/src/main/scala/coursier/ivy/IvyXml.scala +++ b/core/shared/src/main/scala/coursier/ivy/IvyXml.scala @@ -106,7 +106,25 @@ object IvyXml { publicationsOpt = publicationsNodeOpt.map(publications) - } yield + } yield { + + val description = infoNode.children + .find(_.label == "description") + .map(_.textContent.trim) + .getOrElse("") + + val licenses = infoNode.children + .filter(_.label == "license") + .flatMap { n => + n.attribute("name").toOption.map { name => + (name, n.attribute("url").toOption) + }.toSeq + } + + val publicationDate = infoNode.attribute("publication") + .toOption + .flatMap(parseDateTime) + Project( module, version, @@ -128,7 +146,15 @@ object IvyXml { configurations0.flatMap { case (conf, _) => (publicationsOpt.flatMap(_.get(conf)).getOrElse(Nil) ++ inAllConfs).map(conf -> _) } - } + }, + Info( + description, + "", + licenses, + Nil, + publicationDate + ) ) + } } \ No newline at end of file diff --git a/core/shared/src/main/scala/coursier/maven/Pom.scala b/core/shared/src/main/scala/coursier/maven/Pom.scala index 06ece5f2f..f7d893a5d 100644 --- a/core/shared/src/main/scala/coursier/maven/Pom.scala +++ b/core/shared/src/main/scala/coursier/maven/Pom.scala @@ -177,39 +177,73 @@ object Pom { (mod.copy(attributes = Map.empty), ver) -> mod.attributes }.toMap - } yield Project( - projModule.copy(organization = groupId), - version, - deps.map { - case (config, dep0) => - val dep = extraAttrsMap.get(dep0.moduleVersion).fold(dep0)(attrs => - dep0.copy(module = dep0.module.copy(attributes = attrs)) - ) - config -> dep - }, - Map.empty, - parentModuleOpt.map((_, parentVersionOpt.getOrElse(""))), - depMgmts, - properties.toMap, - profiles, - None, - None, - Nil - ) - } + } yield { - def parseDateTime(s: String): Option[Versions.DateTime] = - if (s.length == 14 && s.forall(_.isDigit)) - Some(Versions.DateTime( - s.substring(0, 4).toInt, - s.substring(4, 6).toInt, - s.substring(6, 8).toInt, - s.substring(8, 10).toInt, - s.substring(10, 12).toInt, - s.substring(12, 14).toInt - )) - else - None + val description = pom.children + .find(_.label == "description") + .map(_.textContent) + .getOrElse("") + + val homePage = pom.children + .find(_.label == "url") + .map(_.textContent) + .getOrElse("") + + val licenses = pom.children + .find(_.label == "licenses") + .toSeq + .flatMap(_.children) + .filter(_.label == "license") + .flatMap { n => + n.attribute("name").toOption.map { name => + (name, n.attribute("url").toOption) + }.toSeq + } + + val developers = pom.children + .find(_.label == "developers") + .toSeq + .flatMap(_.children) + .filter(_.label == "developer") + .map { n => + for { + id <- n.attribute("id") + name <- n.attribute("name") + url <- n.attribute("url") + } yield Info.Developer(id, name, url) + } + .collect { + case \/-(d) => d + } + + Project( + projModule.copy(organization = groupId), + version, + deps.map { + case (config, dep0) => + val dep = extraAttrsMap.get(dep0.moduleVersion).fold(dep0)(attrs => + dep0.copy(module = dep0.module.copy(attributes = attrs)) + ) + config -> dep + }, + Map.empty, + parentModuleOpt.map((_, parentVersionOpt.getOrElse(""))), + depMgmts, + properties.toMap, + profiles, + None, + None, + Nil, + Info( + description, + homePage, + licenses, + developers, + None + ) + ) + } + } def versions(node: Node): String \/ Versions = { import Scalaz._ diff --git a/core/shared/src/main/scala/coursier/package.scala b/core/shared/src/main/scala/coursier/package.scala index ec15aabc6..8a19dd3cc 100644 --- a/core/shared/src/main/scala/coursier/package.scala +++ b/core/shared/src/main/scala/coursier/package.scala @@ -37,6 +37,9 @@ package object coursier { type Project = core.Project val Project = core.Project + type Info = core.Info + val Info = core.Info + type Profile = core.Profile val Profile = core.Profile diff --git a/core/shared/src/main/scala/coursier/util/Xml.scala b/core/shared/src/main/scala/coursier/util/Xml.scala index 83fe20f9b..a7469d659 100644 --- a/core/shared/src/main/scala/coursier/util/Xml.scala +++ b/core/shared/src/main/scala/coursier/util/Xml.scala @@ -1,5 +1,7 @@ package coursier.util +import coursier.core.Versions + import scalaz.{\/-, -\/, \/, Scalaz} object Xml { @@ -55,4 +57,17 @@ object Xml { .toRightDisjunction(s"$description not found") } + def parseDateTime(s: String): Option[Versions.DateTime] = + if (s.length == 14 && s.forall(_.isDigit)) + Some(Versions.DateTime( + s.substring(0, 4).toInt, + s.substring(4, 6).toInt, + s.substring(6, 8).toInt, + s.substring(8, 10).toInt, + s.substring(10, 12).toInt, + s.substring(12, 14).toInt + )) + else + None + } diff --git a/plugin/src/main/scala/coursier/FromSbt.scala b/plugin/src/main/scala/coursier/FromSbt.scala index b2e22ba00..ee4940471 100644 --- a/plugin/src/main/scala/coursier/FromSbt.scala +++ b/plugin/src/main/scala/coursier/FromSbt.scala @@ -101,7 +101,8 @@ object FromSbt { Nil, None, None, - Nil + Nil, + Info.empty ) } diff --git a/plugin/src/main/scala/coursier/ToSbt.scala b/plugin/src/main/scala/coursier/ToSbt.scala index 21b8308c3..8839f8ffa 100644 --- a/plugin/src/main/scala/coursier/ToSbt.scala +++ b/plugin/src/main/scala/coursier/ToSbt.scala @@ -1,5 +1,7 @@ package coursier +import java.util.GregorianCalendar + import sbt._ object ToSbt { @@ -24,33 +26,61 @@ object ToSbt { Map.empty ) - def moduleReport(dependency: Dependency, artifacts: Seq[(Artifact, Option[File])]): sbt.ModuleReport = + def moduleReport( + dependency: Dependency, + dependees: Seq[(Dependency, Project)], + project: Project, + artifacts: Seq[(Artifact, Option[File])] + ): sbt.ModuleReport = { + + val sbtArtifacts = artifacts.collect { + case (artifact, Some(file)) => + (ToSbt.artifact(dependency.module, artifact), file) + } + val sbtMissingArtifacts = artifacts.collect { + case (artifact, None) => + ToSbt.artifact(dependency.module, artifact) + } + + val publicationDate = project.info.publication.map { dt => + new GregorianCalendar(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second).getTime + } + + val callers = dependees.map { + case (dependee, dependeeProj) => + new Caller( + ToSbt.moduleId(dependee), + dependeeProj.configurations.keys.toVector, + dependee.module.attributes ++ dependeeProj.properties, + // FIXME Set better values here + isForceDependency = false, + isChangingDependency = false, + isTransitiveDependency = false, + isDirectlyForceDependency = false + ) + } + new sbt.ModuleReport( - ToSbt.moduleId(dependency), - artifacts.collect { - case (artifact, Some(file)) => - (ToSbt.artifact(dependency.module, artifact), file) - }, - artifacts.collect { - case (artifact, None) => - ToSbt.artifact(dependency.module, artifact) - }, - None, - None, - None, - None, - false, - None, - None, - None, - None, - Map.empty, - None, - None, - Nil, - Nil, - Nil + module = ToSbt.moduleId(dependency), + artifacts = sbtArtifacts, + missingArtifacts = sbtMissingArtifacts, + status = None, + publicationDate = publicationDate, + resolver = None, + artifactResolver = None, + evicted = false, + evictedData = None, + evictedReason = None, + problem = None, + homepage = Some(project.info.homePage).filter(_.nonEmpty), + extraAttributes = dependency.module.attributes ++ project.properties, + isDefault = None, + branch = None, + configurations = project.configurations.keys.toVector, + licenses = project.info.licenses, + callers = callers ) + } private def grouped[K, V](map: Seq[(K, V)]): Map[K, Seq[V]] = map.groupBy { case (k, _) => k }.map { @@ -71,9 +101,47 @@ object ToSbt { val groupedDepArtifacts = grouped(depArtifacts) + val versions = res.dependencies.toVector.map { dep => + dep.module -> dep.version + }.toMap + + def clean(dep: Dependency): Dependency = + dep.copy(configuration = "", exclusions = Set.empty, optional = false) + + val reverseDependencies = res.reverseDependencies + .toVector + .map { case (k, v) => + clean(k) -> v.map(clean) + } + .groupBy { case (k, v) => k } + .mapValues { v => + v.flatMap { + case (_, l) => l + } + } + .toVector + .toMap + groupedDepArtifacts.map { case (dep, artifacts) => - ToSbt.moduleReport(dep, artifacts.map(a => a -> artifactFileOpt(a))) + val (_, proj) = res.projectCache(dep.moduleVersion) + + // FIXME Likely flaky... + val dependees = reverseDependencies + .getOrElse(clean(dep.copy(version = "")), Vector.empty) + .map { dependee0 => + val version = versions(dependee0.module) + val dependee = dependee0.copy(version = version) + val (_, dependeeProj) = res.projectCache(dependee.moduleVersion) + (dependee, dependeeProj) + } + + ToSbt.moduleReport( + dep, + dependees, + proj, + artifacts.map(a => a -> artifactFileOpt(a)) + ) } } diff --git a/tests/shared/src/test/scala/coursier/test/package.scala b/tests/shared/src/test/scala/coursier/test/package.scala index 83d17cf7d..1bb6d4487 100644 --- a/tests/shared/src/test/scala/coursier/test/package.scala +++ b/tests/shared/src/test/scala/coursier/test/package.scala @@ -56,7 +56,8 @@ package object test { profiles, versions, snapshotVersioning, - publications + publications, + Info.empty ) } }