From 0418c790b651ff57319d5912ee06586fb0ef793b Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 7 Jul 2015 12:29:24 +0200 Subject: [PATCH 1/5] Snapshot versioning Fixes https://github.com/alexarchambault/coursier/issues/61 --- .../scala/coursier/core/MavenRepository.scala | 10 +- .../scala/coursier/core/Definitions.scala | 22 ++- .../main/scala/coursier/core/Repository.scala | 140 +++++++++++++++++- core/src/main/scala/coursier/core/Xml.scala | 125 ++++++++++++++-- .../test/scala/coursier/test/package.scala | 5 +- 5 files changed, 279 insertions(+), 23 deletions(-) diff --git a/core-jvm/src/main/scala/coursier/core/MavenRepository.scala b/core-jvm/src/main/scala/coursier/core/MavenRepository.scala index 5b39acf82..32b182b38 100644 --- a/core-jvm/src/main/scala/coursier/core/MavenRepository.scala +++ b/core-jvm/src/main/scala/coursier/core/MavenRepository.scala @@ -49,7 +49,15 @@ case class MavenRepository( val url = new URL(urlStr) def log = Task(logger.foreach(_.downloading(urlStr))) - def get = MavenRepository.readFully(url.openStream()) + def get = { + val conn = url.openConnection() + // Dummy user-agent instead of the default "Java/...", + // so that we are not returned incomplete/erroneous metadata + // (Maven 2 compatibility? - happens for snapshot versioning metadata, + // this is SO FUCKING CRAZY) + conn.setRequestProperty("User-Agent", "") + MavenRepository.readFully(conn.getInputStream()) + } log.flatMap(_ => get) } diff --git a/core/src/main/scala/coursier/core/Definitions.scala b/core/src/main/scala/coursier/core/Definitions.scala index 504bf09af..130b48092 100644 --- a/core/src/main/scala/coursier/core/Definitions.scala +++ b/core/src/main/scala/coursier/core/Definitions.scala @@ -56,7 +56,8 @@ case class Project( dependencyManagement: Seq[Dependency], properties: Map[String, String], profiles: Seq[Profile], - versions: Option[Versions] + versions: Option[Versions], + snapshotVersioning: Option[SnapshotVersioning] ) { def moduleVersion = (module, version) } @@ -99,6 +100,25 @@ object Versions { ) } +case class SnapshotVersion( + classifier: String, + extension: String, + value: String, + updated: Option[Versions.DateTime] +) + +case class SnapshotVersioning( + module: Module, + version: String, + latest: String, + release: String, + timestamp: String, + buildNumber: Option[Int], + localCopy: Option[Boolean], + lastUpdated: Option[Versions.DateTime], + snapshotVersions: Seq[SnapshotVersion] +) + case class Artifact( url: String, checksumUrls: Map[String, String], diff --git a/core/src/main/scala/coursier/core/Repository.scala b/core/src/main/scala/coursier/core/Repository.scala index eb8ac7350..61455c96d 100644 --- a/core/src/main/scala/coursier/core/Repository.scala +++ b/core/src/main/scala/coursier/core/Repository.scala @@ -101,6 +101,7 @@ object Repository { case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source { import Repository._ + import BaseMavenRepository._ def artifacts( dependency: Dependency, @@ -108,7 +109,7 @@ case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source { ): Seq[Artifact] = { def ivyLikePath0(subDir: String, baseSuffix: String, ext: String) = - BaseMavenRepository.ivyLikePath( + ivyLikePath( dependency.module.organization, dependency.module.name, project.version, @@ -120,12 +121,20 @@ case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source { val path = if (ivyLike) ivyLikePath0(dependency.attributes.`type` + "s", "", dependency.attributes.`type`) - else + else { + val versioning = + project + .snapshotVersioning + .flatMap(versioning => + mavenVersioning(versioning, dependency.attributes.classifier, dependency.attributes.`type`) + ) + dependency.module.organization.split('.').toSeq ++ Seq( dependency.module.name, project.version, - s"${dependency.module.name}-${project.version}${Some(dependency.attributes.classifier).filter(_.nonEmpty).map("-"+_).mkString}.${dependency.attributes.`type`}" + s"${dependency.module.name}-${versioning getOrElse project.version}${Some(dependency.attributes.classifier).filter(_.nonEmpty).map("-"+_).mkString}.${dependency.attributes.`type`}" ) + } var artifact = Artifact( @@ -139,6 +148,9 @@ case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source { if (dependency.attributes.`type` == "jar") { artifact = artifact.withDefaultSignature + // FIXME Snapshot versioning of sources and javadoc is not taken into account here. + // Will be ok if it's the same as the main JAR though. + artifact = if (ivyLike) { val srcPath = root + ivyLikePath0("srcs", "-sources", "jar").mkString("/") @@ -181,6 +193,17 @@ object BaseMavenRepository { s"$name$baseSuffix.$ext" ) + def mavenVersioning( + snapshotVersioning: SnapshotVersioning, + classifier: String, + extension: String + ): Option[String] = + snapshotVersioning + .snapshotVersions + .find(v => v.classifier == classifier && v.extension == extension) + .map(_.value) + .filter(_.nonEmpty) + } abstract class BaseMavenRepository( @@ -198,16 +221,27 @@ abstract class BaseMavenRepository( val source = MavenSource(root, ivyLike) - def projectArtifact(module: Module, version: String): Artifact = { + def projectArtifact( + module: Module, + version: String, + versioningValue: Option[String] + ): Artifact = { val path = ( if (ivyLike) - ivyLikePath(module.organization, module.name, version, "poms", "", "pom") + ivyLikePath( + module.organization, + module.name, + versioningValue getOrElse version, + "poms", + "", + "pom" + ) else module.organization.split('.').toSeq ++ Seq( module.name, version, - s"${module.name}-$version.pom" + s"${module.name}-${versioningValue getOrElse version}.pom" ) ) .map(encodeURIComponent) @@ -242,6 +276,32 @@ abstract class BaseMavenRepository( Some(artifact) } + def snapshotVersioningArtifact( + module: Module, + version: String + ): Option[Artifact] = + if (ivyLike) None + else { + val path = ( + module.organization.split('.').toSeq ++ Seq( + module.name, + version, + "maven-metadata.xml" + ) + ) .map(encodeURIComponent) + + val artifact = + Artifact( + path.mkString("/"), + Map.empty, + Map.empty, + Attributes("pom", "") + ) + .withDefaultChecksums + + Some(artifact) + } + def versions( module: Module, cachePolicy: CachePolicy = CachePolicy.Default @@ -265,14 +325,80 @@ abstract class BaseMavenRepository( ) } + def snapshotVersioning( + module: Module, + version: String, + cachePolicy: CachePolicy = CachePolicy.Default + ): EitherT[Task, String, SnapshotVersioning] = { + + EitherT( + snapshotVersioningArtifact(module, version) match { + case None => Task.now(-\/("Not supported")) + case Some(artifact) => + fetch(artifact, cachePolicy) + .run + .map(eitherStr => + for { + str <- eitherStr + xml <- \/.fromEither(compatibility.xmlParse(str)) + _ <- if (xml.label == "metadata") \/-(()) else -\/("Metadata not found") + snapshotVersioning <- Xml.snapshotVersioning(xml) + } yield snapshotVersioning + ) + } + ) + } + def findNoInterval( module: Module, version: String, cachePolicy: CachePolicy + ): EitherT[Task, String, Project] = + EitherT{ + def withSnapshotVersioning = + snapshotVersioning(module, version, cachePolicy) + .flatMap { snapshotVersioning => + val versioningOption = + mavenVersioning(snapshotVersioning, "", "jar") + .orElse(mavenVersioning(snapshotVersioning, "", "")) + + versioningOption match { + case None => + EitherT[Task, String, Project]( + Task.now(-\/("No snapshot versioning value found")) + ) + case versioning @ Some(_) => + findVersioning(module, version, versioning, cachePolicy) + .map(_.copy(snapshotVersioning = Some(snapshotVersioning))) + } + } + + findVersioning(module, version, None, cachePolicy) + .run + .flatMap{ eitherProj => + if (eitherProj.isLeft) + withSnapshotVersioning + .run + .map(eitherProj0 => + if (eitherProj0.isLeft) + eitherProj + else + eitherProj0 + ) + else + Task.now(eitherProj) + } + } + + def findVersioning( + module: Module, + version: String, + versioningValue: Option[String], + cachePolicy: CachePolicy ): EitherT[Task, String, Project] = { EitherT { - fetch(projectArtifact(module, version), cachePolicy) + fetch(projectArtifact(module, version, versioningValue), cachePolicy) .run .map(eitherStr => for { diff --git a/core/src/main/scala/coursier/core/Xml.scala b/core/src/main/scala/coursier/core/Xml.scala index caa2a3156..9bd94700e 100644 --- a/core/src/main/scala/coursier/core/Xml.scala +++ b/core/src/main/scala/coursier/core/Xml.scala @@ -210,10 +210,24 @@ object Xml { depMgmts, properties.toMap, profiles, + None, None ) } + 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 + def versions(node: Node): String \/ Versions = { import Scalaz._ @@ -230,24 +244,111 @@ object Xml { release = text(xmlVersioning, "release", "Release version") .getOrElse("") - versions <- xmlVersioning.child + versionsOpt = xmlVersioning.child .find(_.label == "versions") .map(_.child.filter(_.label == "version").flatMap(_.child.collectFirst{case Text(t) => t})) - .toRightDisjunction("Version list not found in metadata") lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time") .toOption - .filter(s => s.length == 14 && s.forall(_.isDigit)) - .map(s => 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 - )) + .flatMap(parseDateTime) - } yield Versions(latest, release, versions.toList, lastUpdatedOpt) + } yield Versions(latest, release, versionsOpt.map(_.toList).getOrElse(Nil), lastUpdatedOpt) + } + + def snapshotVersion(node: Node): String \/ SnapshotVersion = { + def textOrEmpty(name: String, desc: String) = + text(node, name, desc) + .toOption + .getOrElse("") + + val classifier = textOrEmpty("classifier", "Classifier") + val ext = textOrEmpty("extension", "Extensions") + val value = textOrEmpty("value", "Value") + + val updatedOpt = text(node, "updated", "Updated") + .toOption + .flatMap(parseDateTime) + + \/-(SnapshotVersion( + classifier, + ext, + value, + updatedOpt + )) + } + + def snapshotVersioning(node: Node): String \/ SnapshotVersioning = { + import Scalaz._ + + // FIXME Quite similar to Versions above + for { + organization <- text(node, "groupId", "Organization") + name <- text(node, "artifactId", "Name") + version = readVersion(node) + + xmlVersioning <- node.child + .find(_.label == "versioning") + .toRightDisjunction("Versioning info not found in metadata") + + latest = text(xmlVersioning, "latest", "Latest version") + .getOrElse("") + release = text(xmlVersioning, "release", "Release version") + .getOrElse("") + + versionsOpt = xmlVersioning.child + .find(_.label == "versions") + .map(_.child.filter(_.label == "version").flatMap(_.child.collectFirst{case Text(t) => t})) + + lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time") + .toOption + .flatMap(parseDateTime) + + xmlSnapshotOpt = xmlVersioning.child + .find(_.label == "snapshot") + + timestamp = xmlSnapshotOpt + .flatMap( + text(_, "timestamp", "Snapshot timestamp") + .toOption + ) + .getOrElse("") + + buildNumber = xmlSnapshotOpt + .flatMap( + text(_, "buildNumber", "Snapshot build number") + .toOption + ) + .filter(s => s.nonEmpty && s.forall(_.isDigit)) + .map(_.toInt) + + localCopy = xmlSnapshotOpt + .flatMap( + text(_, "localCopy", "Snapshot local copy") + .toOption + ) + .collect{ + case "true" => true + case "false" => false + } + + xmlSnapshotVersions = xmlVersioning.child + .find(_.label == "snapshotVersions") + .map(_.child.filter(_.label == "snapshotVersion")) + .getOrElse(Seq.empty) + snapshotVersions <- xmlSnapshotVersions + .toList + .traverseU(snapshotVersion) + } yield SnapshotVersioning( + Module(organization, name), + version, + latest, + release, + timestamp, + buildNumber, + localCopy, + lastUpdatedOpt, + snapshotVersions + ) } } diff --git a/core/src/test/scala/coursier/test/package.scala b/core/src/test/scala/coursier/test/package.scala index b2feafef9..ebcf0b6ab 100644 --- a/core/src/test/scala/coursier/test/package.scala +++ b/core/src/test/scala/coursier/test/package.scala @@ -30,7 +30,8 @@ package object test { dependencyManagement: Seq[Dependency] = Seq.empty, properties: Map[String, String] = Map.empty, profiles: Seq[Profile] = Seq.empty, - versions: Option[core.Versions] = None): Project = - core.Project(module, version, dependencies, parent, dependencyManagement, properties, profiles, versions) + versions: Option[core.Versions] = None, + snapshotVersioning: Option[core.SnapshotVersioning] = None): Project = + core.Project(module, version, dependencies, parent, dependencyManagement, properties, profiles, versions, snapshotVersioning) } } From a1be48eeacb55438d8b2437701dd17c4ff7cd303 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 7 Jul 2015 12:29:25 +0200 Subject: [PATCH 2/5] Fix --- cli/src/main/scala/coursier/cli/Coursier.scala | 16 ++++++---------- files/src/main/scala/coursier/Cache.scala | 8 ++------ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cli/src/main/scala/coursier/cli/Coursier.scala b/cli/src/main/scala/coursier/cli/Coursier.scala index e6db55133..1c6057c17 100644 --- a/cli/src/main/scala/coursier/cli/Coursier.scala +++ b/cli/src/main/scala/coursier/cli/Coursier.scala @@ -121,13 +121,11 @@ case class Coursier( repository0 } - val existingRepo = cache - .list() - .map(_._1) - .toSet - if (repositoryIds.exists(!existingRepo(_))) { + val repoMap = cache.map() + + if (repositoryIds.exists(!repoMap.contains(_))) { val notFound = repositoryIds - .filter(!existingRepo(_)) + .filter(!repoMap.contains(_)) Console.err.println( (if (notFound.lengthCompare(1) == 1) "Repository" else "Repositories") + @@ -138,10 +136,8 @@ case class Coursier( sys.exit(1) } - - val (repositories0, fileCaches) = cache - .list() - .map{case (_, repo, cacheEntry) => (repo, cacheEntry)} + val (repositories0, fileCaches) = repositoryIds + .map(repoMap) .unzip val repositories = repositories0 diff --git a/files/src/main/scala/coursier/Cache.scala b/files/src/main/scala/coursier/Cache.scala index 8de434f51..df127b3dc 100644 --- a/files/src/main/scala/coursier/Cache.scala +++ b/files/src/main/scala/coursier/Cache.scala @@ -129,11 +129,7 @@ case class Cache(cache: File) { } else Nil - def files(): Files = { - val map0 = map() - val default0 = default() - - new Files(default0.map(map0(_)._2), () => ???) - } + def files(): Files = + new Files(list().map{case (_, _, matching) => matching }, () => ???) } From 7f774319e61443749dd96ef1cf227d8cc0c6e1ed Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 7 Jul 2015 12:29:26 +0200 Subject: [PATCH 3/5] Better file URI handling Next step would be to make Artifact generic, and use java.net.URI / java.io.File instead of strings. --- .../src/main/scala/coursier/core/MavenRepository.scala | 2 +- core/src/main/scala/coursier/core/Repository.scala | 4 +++- files/src/main/scala/coursier/Cache.scala | 2 +- files/src/main/scala/coursier/Files.scala | 10 ++++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/core-jvm/src/main/scala/coursier/core/MavenRepository.scala b/core-jvm/src/main/scala/coursier/core/MavenRepository.scala index 32b182b38..81e2bf9f3 100644 --- a/core-jvm/src/main/scala/coursier/core/MavenRepository.scala +++ b/core-jvm/src/main/scala/coursier/core/MavenRepository.scala @@ -15,7 +15,7 @@ case class MavenRepository( logger: Option[MavenRepository.Logger] = None ) extends BaseMavenRepository(root, ivyLike) { - val isLocal = root.startsWith("file:///") + val isLocal = root.startsWith("file:/") def fetch( artifact: Artifact, diff --git a/core/src/main/scala/coursier/core/Repository.scala b/core/src/main/scala/coursier/core/Repository.scala index 61455c96d..7283f244c 100644 --- a/core/src/main/scala/coursier/core/Repository.scala +++ b/core/src/main/scala/coursier/core/Repository.scala @@ -3,6 +3,8 @@ package coursier.core import scalaz.{ -\/, \/-, \/, EitherT } import scalaz.concurrent.Task +import java.io.File + import coursier.core.compatibility.encodeURIComponent trait Repository { @@ -21,7 +23,7 @@ object Repository { val sonatypeReleases = MavenRepository("https://oss.sonatype.org/content/repositories/releases/") val sonatypeSnapshots = MavenRepository("https://oss.sonatype.org/content/repositories/snapshots/") - lazy val ivy2Local = MavenRepository("file://" + sys.props("user.home") + "/.ivy2/local/", ivyLike = true) + lazy val ivy2Local = MavenRepository(new File(sys.props("user.home") + "/.ivy2/local/").toURI.toString, ivyLike = true) /** diff --git a/files/src/main/scala/coursier/Cache.scala b/files/src/main/scala/coursier/Cache.scala index df127b3dc..cc1cac06e 100644 --- a/files/src/main/scala/coursier/Cache.scala +++ b/files/src/main/scala/coursier/Cache.scala @@ -61,7 +61,7 @@ case class Cache(cache: File) { add("central", "https://repo1.maven.org/maven2/", ivyLike = false) def addIvy2Local(): Unit = - add("ivy2local", "file://" + sys.props("user.home") + "/.ivy2/local/", ivyLike = true) + add("ivy2local", new File(sys.props("user.home") + "/.ivy2/local/").toURI.toString, ivyLike = true) def init(ifEmpty: Boolean = true): Unit = if (!ifEmpty || !cache.exists()) { diff --git a/files/src/main/scala/coursier/Files.scala b/files/src/main/scala/coursier/Files.scala index 830b0e216..1bc4b8ed3 100644 --- a/files/src/main/scala/coursier/Files.scala +++ b/files/src/main/scala/coursier/Files.scala @@ -23,12 +23,14 @@ case class Files( def withLocal(artifact: Artifact): Artifact = { val isLocal = - artifact.url.startsWith("file://") && - artifact.checksumUrls.values.forall(_.startsWith("file://")) + artifact.url.startsWith("file:/") && + artifact.checksumUrls.values.forall(_.startsWith("file:/")) def local(url: String) = - if (url.startsWith("file://")) + if (url.startsWith("file:///")) url.stripPrefix("file://") + else if (url.startsWith("file:/")) + url.stripPrefix("file:") else cache.find{case (base, _) => url.startsWith(base)} match { case None => ??? @@ -139,7 +141,7 @@ case class Files( val tasks = - for ((f, url) <- pairs if url != ("file://" + f)) yield { + for ((f, url) <- pairs if url != ("file:" + f) && url != ("file://" + f)) yield { val file = new File(f) cachePolicy(locally(file))(remote(file, url)) .map(e => (file, url) -> e.map(_ => ())) From 72d29f52233fdb802a9d0ad9f985ad7118a81b23 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 7 Jul 2015 12:29:26 +0200 Subject: [PATCH 4/5] Message when initializing cache --- cli/src/main/scala/coursier/cli/Coursier.scala | 1 + cli/src/main/scala/coursier/cli/Repositories.scala | 2 +- files/src/main/scala/coursier/Cache.scala | 7 ++++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cli/src/main/scala/coursier/cli/Coursier.scala b/cli/src/main/scala/coursier/cli/Coursier.scala index 1c6057c17..fbfe02217 100644 --- a/cli/src/main/scala/coursier/cli/Coursier.scala +++ b/cli/src/main/scala/coursier/cli/Coursier.scala @@ -108,6 +108,7 @@ case class Coursier( CachePolicy.Default val cache = Cache.default + cache.init(verbose = verbose0 >= 0) val repositoryIds = { val repository0 = repository diff --git a/cli/src/main/scala/coursier/cli/Repositories.scala b/cli/src/main/scala/coursier/cli/Repositories.scala index 37903016f..4e717fa03 100644 --- a/cli/src/main/scala/coursier/cli/Repositories.scala +++ b/cli/src/main/scala/coursier/cli/Repositories.scala @@ -40,7 +40,7 @@ case class Repositories( } if (!cache.cache.exists()) - cache.init() + cache.init(verbose = true) val current = cache.list().map(_._1).toSet diff --git a/files/src/main/scala/coursier/Cache.scala b/files/src/main/scala/coursier/Cache.scala index cc1cac06e..b33f9151e 100644 --- a/files/src/main/scala/coursier/Cache.scala +++ b/files/src/main/scala/coursier/Cache.scala @@ -63,8 +63,13 @@ case class Cache(cache: File) { def addIvy2Local(): Unit = add("ivy2local", new File(sys.props("user.home") + "/.ivy2/local/").toURI.toString, ivyLike = true) - def init(ifEmpty: Boolean = true): Unit = + def init( + ifEmpty: Boolean = true, + verbose: Boolean = false + ): Unit = if (!ifEmpty || !cache.exists()) { + if (verbose) + Console.err.println(s"Initializing $cache") repoDir.mkdirs() metadataBase.mkdirs() fileBase.mkdirs() From f45c2b0aaa4b4cd4635007d52205e713657bb7d8 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 7 Jul 2015 12:29:27 +0200 Subject: [PATCH 5/5] Better error printing --- cli/src/main/scala/coursier/cli/Coursier.scala | 12 ++++++------ .../main/scala/coursier/core/MavenRepository.scala | 12 ++++++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/cli/src/main/scala/coursier/cli/Coursier.scala b/cli/src/main/scala/coursier/cli/Coursier.scala index fbfe02217..da6584b85 100644 --- a/cli/src/main/scala/coursier/cli/Coursier.scala +++ b/cli/src/main/scala/coursier/cli/Coursier.scala @@ -53,7 +53,7 @@ case class Coursier( println(s"Downloading $url") def downloaded(url: String, success: Boolean) = if (!success) - println(s"Failed to download $url") + println(s"Failed: $url") def readingFromCache(f: File) = {} def puttingInCache(f: File) = {} @@ -62,7 +62,7 @@ case class Coursier( println(s"Downloading $url") def downloadedArtifact(url: String, success: Boolean) = if (!success) - println(s"Failed to download $url") + println(s"Failed: $url") } def verboseLogger: MavenRepository.Logger with Files.Logger = @@ -72,7 +72,7 @@ case class Coursier( def downloaded(url: String, success: Boolean) = println( if (success) s"Downloaded $url" - else s"Failed to download $url" + else s"Failed: $url" ) def readingFromCache(f: File) = { println(s"Reading ${fileRepr(f)} from cache") @@ -87,7 +87,7 @@ case class Coursier( def downloadedArtifact(url: String, success: Boolean) = println( if (success) s"Downloaded $url" - else s"Failed to download $url" + else s"Failed: $url" ) } @@ -220,9 +220,9 @@ case class Coursier( val errors = res.errors if (errors.nonEmpty) { - println(s"${errors.size} error(s):") + println(s"\n${errors.size} error(s):") for ((dep, errs) <- errors) { - println(s" ${dep.module}:\n ${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}") + println(s" ${dep.module}:${dep.version}:\n${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}") } } diff --git a/core-jvm/src/main/scala/coursier/core/MavenRepository.scala b/core-jvm/src/main/scala/coursier/core/MavenRepository.scala index 81e2bf9f3..9fb150d84 100644 --- a/core-jvm/src/main/scala/coursier/core/MavenRepository.scala +++ b/core-jvm/src/main/scala/coursier/core/MavenRepository.scala @@ -58,8 +58,11 @@ case class MavenRepository( conn.setRequestProperty("User-Agent", "") MavenRepository.readFully(conn.getInputStream()) } + def logEnd(success: Boolean) = logger.foreach(_.downloaded(urlStr, success)) - log.flatMap(_ => get) + log + .flatMap(_ => get) + .map{ res => logEnd(res.isRight); res } } def save(s: String) = { @@ -115,7 +118,12 @@ object MavenRepository { finally is0.close() new String(b, "UTF-8") - } .leftMap(_.getMessage) + } .leftMap{ + case e: java.io.FileNotFoundException => + s"Not found: ${e.getMessage}" + case e => + s"$e: ${e.getMessage}" + } } }