diff --git a/ivy/src/main/java/sbt/SbtExtraProperties.java b/ivy/src/main/java/sbt/mavenint/SbtPomExtraProperties.java similarity index 93% rename from ivy/src/main/java/sbt/SbtExtraProperties.java rename to ivy/src/main/java/sbt/mavenint/SbtPomExtraProperties.java index bcc9b8189..fde6af15b 100644 --- a/ivy/src/main/java/sbt/SbtExtraProperties.java +++ b/ivy/src/main/java/sbt/mavenint/SbtPomExtraProperties.java @@ -1,9 +1,9 @@ -package sbt; +package sbt.mavenint; /** * Extra properties we dump from Aether into the properties list. */ -public class SbtExtraProperties { +public class SbtPomExtraProperties { public static final String MAVEN_PACKAGING_KEY = "sbt.pom.packaging"; public static final String SCALA_VERSION_KEY = "sbt.pom.scalaversion"; diff --git a/ivy/src/main/scala/sbt/CustomPomParser.scala b/ivy/src/main/scala/sbt/CustomPomParser.scala index c9e660833..d9ceff32a 100644 --- a/ivy/src/main/scala/sbt/CustomPomParser.scala +++ b/ivy/src/main/scala/sbt/CustomPomParser.scala @@ -12,8 +12,7 @@ import org.apache.ivy.util.extendable.ExtendableItem import java.io.{ File, InputStream } import java.net.URL import java.util.regex.Pattern - -import org.apache.maven.repository.internal.{ PomExtraDependencyAttributes } +import sbt.mavenint.{ PomExtraDependencyAttributes, SbtPomExtraProperties } @deprecated("0.13.8", "We now use an Aether-based pom parser.") final class CustomPomParser(delegate: ModuleDescriptorParser, transform: (ModuleDescriptorParser, ModuleDescriptor) => ModuleDescriptor) extends ModuleDescriptorParser { @@ -36,8 +35,8 @@ object CustomPomParser { ReplaceMavenConfigurationMappings.init() /** The key prefix that indicates that this is used only to store extra information and is not intended for dependency resolution.*/ - val InfoKeyPrefix = SbtExtraProperties.POM_INFO_KEY_PREFIX - val ApiURLKey = SbtExtraProperties.POM_API_KEY + val InfoKeyPrefix = SbtPomExtraProperties.POM_INFO_KEY_PREFIX + val ApiURLKey = SbtPomExtraProperties.POM_API_KEY val SbtVersionKey = PomExtraDependencyAttributes.SbtVersionKey val ScalaVersionKey = PomExtraDependencyAttributes.ScalaVersionKey diff --git a/ivy/src/main/scala/sbt/MakePom.scala b/ivy/src/main/scala/sbt/MakePom.scala index fc15a3cd9..2cb7331e6 100644 --- a/ivy/src/main/scala/sbt/MakePom.scala +++ b/ivy/src/main/scala/sbt/MakePom.scala @@ -9,7 +9,7 @@ package sbt import java.io.File -import org.apache.maven.repository.internal.PomExtraDependencyAttributes +import sbt.mavenint.PomExtraDependencyAttributes // 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 diff --git a/ivy/src/main/scala/sbt/ModuleID.scala b/ivy/src/main/scala/sbt/ModuleID.scala index 4b244df44..c0e8670d5 100644 --- a/ivy/src/main/scala/sbt/ModuleID.scala +++ b/ivy/src/main/scala/sbt/ModuleID.scala @@ -5,6 +5,8 @@ package sbt import java.net.URL +import sbt.mavenint.SbtPomExtraProperties + final case class ModuleID(organization: String, name: String, revision: String, configurations: Option[String] = None, isChanging: Boolean = false, isTransitive: Boolean = true, isForce: Boolean = false, explicitArtifacts: Seq[Artifact] = Nil, exclusions: Seq[ExclusionRule] = Nil, extraAttributes: Map[String, String] = Map.empty, crossVersion: CrossVersion = CrossVersion.Disabled) { override def toString: String = organization + ":" + name + ":" + revision + @@ -15,7 +17,7 @@ final case class ModuleID(organization: String, name: String, revision: String, def extraString: String = extraDependencyAttributes.map { case (k, v) => k + "=" + v } mkString ("(", ", ", ")") /** Returns the extra attributes except for ones marked as information only (ones that typically would not be used for dependency resolution). */ - def extraDependencyAttributes: Map[String, String] = extraAttributes.filterKeys(!_.startsWith(SbtExtraProperties.POM_INFO_KEY_PREFIX)) + def extraDependencyAttributes: Map[String, String] = extraAttributes.filterKeys(!_.startsWith(SbtPomExtraProperties.POM_INFO_KEY_PREFIX)) @deprecated("Use `cross(CrossVersion)`, the variant accepting a CrossVersion value constructed by a member of the CrossVersion object instead.", "0.12.0") def cross(v: Boolean): ModuleID = cross(if (v) CrossVersion.binary else CrossVersion.Disabled) diff --git a/ivy/src/main/scala/org/apache/maven/repository/internal/PomExtraDependencyAttributes.scala b/ivy/src/main/scala/sbt/mavenint/PomExtraDependencyAttributes.scala similarity index 96% rename from ivy/src/main/scala/org/apache/maven/repository/internal/PomExtraDependencyAttributes.scala rename to ivy/src/main/scala/sbt/mavenint/PomExtraDependencyAttributes.scala index 4c2ead5d1..ab87cae75 100644 --- a/ivy/src/main/scala/org/apache/maven/repository/internal/PomExtraDependencyAttributes.scala +++ b/ivy/src/main/scala/sbt/mavenint/PomExtraDependencyAttributes.scala @@ -1,4 +1,4 @@ -package org.apache.maven.repository.internal +package sbt.mavenint import java.util.Properties import java.util.regex.Pattern @@ -31,7 +31,7 @@ object PomExtraDependencyAttributes { * A map of module id to extra dependency attributes associated with dependencies on that module. */ def readFromAether(props: java.util.Map[String, AnyRef]): Map[ModuleRevisionId, Map[String, String]] = { - import collection.JavaConverters._ + import scala.collection.JavaConverters._ (props.asScala get ExtraAttributesKey) match { case None => Map.empty case Some(str) => @@ -71,7 +71,7 @@ object PomExtraDependencyAttributes { } def qualifiedExtra(item: ExtendableItem): Map[String, String] = { - import collection.JavaConverters._ + import scala.collection.JavaConverters._ item.getQualifiedExtraAttributes.asInstanceOf[java.util.Map[String, String]].asScala.toMap } def filterCustomExtra(item: ExtendableItem, include: Boolean): Map[String, String] = @@ -83,7 +83,7 @@ object PomExtraDependencyAttributes { // This makes the id suitable as a key to associate a dependency parsed from a element // with the extra attributes from the section def simplify(id: ModuleRevisionId): ModuleRevisionId = { - import collection.JavaConverters._ + import scala.collection.JavaConverters._ ModuleRevisionId.newInstance(id.getOrganisation, id.getName, id.getBranch, id.getRevision, filterCustomExtra(id, include = false).asJava) } diff --git a/main/src/main/scala/sbt/APIMappings.scala b/main/src/main/scala/sbt/APIMappings.scala index 40d26709d..a51d81cc9 100644 --- a/main/src/main/scala/sbt/APIMappings.scala +++ b/main/src/main/scala/sbt/APIMappings.scala @@ -3,6 +3,8 @@ package sbt import java.io.File import java.net.{ MalformedURLException, URL } +import sbt.mavenint.SbtPomExtraProperties + private[sbt] object APIMappings { def extract(cp: Seq[Attributed[File]], log: Logger): Seq[(File, URL)] = cp.flatMap(entry => extractFromEntry(entry, log)) @@ -15,7 +17,7 @@ private[sbt] object APIMappings { private[this] def extractFromID(entry: File, mid: ModuleID, log: Logger): Option[(File, URL)] = for { - urlString <- mid.extraAttributes.get(SbtExtraProperties.POM_API_KEY) + urlString <- mid.extraAttributes.get(SbtPomExtraProperties.POM_API_KEY) u <- parseURL(urlString, entry, log) } yield (entry, u) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 21b1ae7bf..dbe40f3e0 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -5,8 +5,8 @@ package sbt import Attributed.data import Scope.{ fillTaskAxis, GlobalScope, ThisScope } -import org.apache.maven.repository.internal.PomExtraDependencyAttributes import sbt.Compiler.InputsWithPrevious +import sbt.mavenint.{ PomExtraDependencyAttributes, SbtPomExtraProperties } import xsbt.api.Discovery import xsbti.compile.CompileOrder import Project.{ inConfig, inScope, inTask, richInitialize, richInitializeTask, richTaskSessionVar } @@ -1216,7 +1216,7 @@ object Classpaths { private[sbt] def defaultProjectID: Initialize[ModuleID] = Def.setting { val base = ModuleID(organization.value, moduleName.value, version.value).cross(crossVersion in projectID value).artifacts(artifacts.value: _*) apiURL.value match { - case Some(u) if autoAPIMappings.value => base.extra(SbtExtraProperties.POM_API_KEY -> u.toExternalForm) + case Some(u) if autoAPIMappings.value => base.extra(SbtPomExtraProperties.POM_API_KEY -> u.toExternalForm) case _ => base } } diff --git a/sbt-maven-resolver/src/main/java/org/apache/maven/repository/internal/SbtArtifactDescriptorReader.java b/sbt-maven-resolver/src/main/java/org/apache/maven/repository/internal/SbtArtifactDescriptorReader.java index 7e45ed94c..63a465cb7 100644 --- a/sbt-maven-resolver/src/main/java/org/apache/maven/repository/internal/SbtArtifactDescriptorReader.java +++ b/sbt-maven-resolver/src/main/java/org/apache/maven/repository/internal/SbtArtifactDescriptorReader.java @@ -81,7 +81,8 @@ import org.eclipse.aether.spi.log.Logger; import org.eclipse.aether.spi.log.LoggerFactory; import org.eclipse.aether.spi.log.NullLoggerFactory; import org.eclipse.aether.transfer.ArtifactNotFoundException; -import sbt.SbtExtraProperties; +import sbt.mavenint.PomExtraDependencyAttributes; +import sbt.mavenint.SbtPomExtraProperties; /** * A hacked version of maven's default artifact descriptor reader which we use in place of the standard aether adapter. @@ -260,12 +261,12 @@ public class SbtArtifactDescriptorReader } List licenses = model.getLicenses(); - properties.put( SbtExtraProperties.LICENSE_COUNT_KEY, licenses.size() ); + properties.put( SbtPomExtraProperties.LICENSE_COUNT_KEY, licenses.size() ); for ( int i = 0; i < licenses.size(); i++ ) { License license = licenses.get( i ); - properties.put( SbtExtraProperties.makeLicenseName(i), license.getName() ); - properties.put( SbtExtraProperties.makeLicenseUrl(i), license.getUrl() ); + properties.put( SbtPomExtraProperties.makeLicenseName(i), license.getName() ); + properties.put( SbtPomExtraProperties.makeLicenseUrl(i), license.getUrl() ); properties.put( "license." + i + ".comments", license.getComments() ); properties.put( "license." + i + ".distribution", license.getDistribution() ); } @@ -273,15 +274,15 @@ public class SbtArtifactDescriptorReader // SBT ADDED - Here we push in the pom packaging type for Ivy expectations. final String packaging = (model.getPackaging() == null) ? "jar" : model.getPackaging(); - properties.put(SbtExtraProperties.MAVEN_PACKAGING_KEY, packaging); + properties.put(SbtPomExtraProperties.MAVEN_PACKAGING_KEY, packaging); // SBT ADDED - Here we inject the sbt/scala version we parse out of the pom. final Properties mprops = model.getProperties(); - if(mprops.containsKey(SbtExtraProperties.POM_SBT_VERSION)) { - final String sbtVersion = mprops.getProperty(SbtExtraProperties.POM_SBT_VERSION); - properties.put(SbtExtraProperties.SBT_VERSION_KEY, sbtVersion); + if(mprops.containsKey(SbtPomExtraProperties.POM_SBT_VERSION)) { + final String sbtVersion = mprops.getProperty(SbtPomExtraProperties.POM_SBT_VERSION); + properties.put(SbtPomExtraProperties.SBT_VERSION_KEY, sbtVersion); } - if(mprops.containsKey(SbtExtraProperties.POM_SCALA_VERSION)) { - properties.put(SbtExtraProperties.SCALA_VERSION_KEY, mprops.getProperty(SbtExtraProperties.POM_SCALA_VERSION)); + if(mprops.containsKey(SbtPomExtraProperties.POM_SCALA_VERSION)) { + properties.put(SbtPomExtraProperties.SCALA_VERSION_KEY, mprops.getProperty(SbtPomExtraProperties.POM_SCALA_VERSION)); } // SBT-Added - Here we inject the additional dependency attributes (for transitive plugin resolution). diff --git a/sbt-maven-resolver/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositorySystemFactory.scala b/sbt-maven-resolver/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositorySystemFactory.scala deleted file mode 100644 index 1946dc0da..000000000 --- a/sbt-maven-resolver/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositorySystemFactory.scala +++ /dev/null @@ -1,160 +0,0 @@ -package org.apache.ivy.plugins.resolver - -import java.net.URI - -import org.apache.ivy.plugins.repository.Resource -import org.apache.ivy.plugins.repository.url.URLResource -import org.apache.ivy.util.Message -import org.apache.ivy.util.url.URLHandlerRegistry -import org.eclipse.aether.artifact.Artifact -import org.eclipse.aether.impl.{ MetadataGeneratorFactory, ArtifactDescriptorReader, RepositoryConnectorProvider, DefaultServiceLocator } -import org.eclipse.aether.metadata.Metadata -import org.eclipse.aether.spi.connector.RepositoryConnectorFactory -import org.eclipse.aether.spi.connector.layout.{ RepositoryLayoutFactory, RepositoryLayoutProvider, RepositoryLayout } -import org.eclipse.aether.spi.connector.layout.RepositoryLayout.Checksum -import org.eclipse.aether.{ - RepositorySystem, - RepositorySystemSession -} - -import org.eclipse.aether.repository.{ RemoteRepository, LocalRepository } -import org.eclipse.aether.RepositorySystemSession -import org.apache.maven.repository.internal._ -import org.eclipse.aether.spi.connector.transport._ -import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory -import java.io.File - -/** Helper methods for dealing with starting up Aether. */ -object MavenRepositorySystemFactory { - def newRepositorySystemImpl: RepositorySystem = { - // For now we just log Aether instantiation issues. These should probably cause fatal errors. - val locator = MavenRepositorySystemUtils.newServiceLocator() - locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler { - override def serviceCreationFailed(tpe: Class[_], impl: Class[_], exception: Throwable): Unit = { - Message.error(s"Failed to create $tpe, of class $impl") - } - }) - // Here we register the Ivy <-> Aether transport bridge - locator.addService(classOf[TransporterFactory], classOf[MyTransportFactory]) - // This connects the download mechanism to our transports. Why is it needed? no clue. - locator.addService(classOf[RepositoryConnectorFactory], classOf[BasicRepositoryConnectorFactory]) - - // Plugins cause issues here, as their layout is super odd. Here we inject a new plugin layout - locator.addService(classOf[RepositoryLayoutFactory], classOf[SbtPluginLayoutFactory]) - - // Here we add the metadata services so aether will automatically add maven-metadata.xml files. - locator.addService(classOf[MetadataGeneratorFactory], classOf[SnapshotMetadataGeneratorFactory]) - locator.addService(classOf[MetadataGeneratorFactory], classOf[VersionsMetadataGeneratorFactory]) - - // Add our hook for parsing pom.xml files. - locator.setService(classOf[ArtifactDescriptorReader], classOf[SbtArtifactDescriptorReader]) - // Finally, use the DI to create our repository system. - locator.getService(classOf[RepositorySystem]) - } - def newSessionImpl(system: RepositorySystem, localRepoDir: File): RepositorySystemSession = { - val session = MavenRepositorySystemUtils.newSession() - val localRepo = new LocalRepository(localRepoDir) - session setLocalRepositoryManager (system.newLocalRepositoryManager(session, localRepo)) - // Here we set a descriptor policy that FORCES the pom.xml to exist, otherwise Ivy's resolution - // algorithm freaks out. What we could do is also do the ivy lame-thing of checking for a JAR - // instead of a pom.xml, but let's see if this is actually a problem in practice. - val descriptorPolicy = new org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy( - /* ignoreMissing */ false, /* ignoreInvalid. */ true) - session.setArtifactDescriptorPolicy(descriptorPolicy) - session - } - - def defaultLocalRepo: java.io.File = { - new java.io.File(s"${sys.props("user.home")}/.m2/repository") - } -} -/** Override aether's default transport with Ivy-ones. */ -class MyTransportFactory extends TransporterFactory { - override def newInstance(session: RepositorySystemSession, repository: RemoteRepository): Transporter = - repository.getProtocol match { - case "http" | "https" => new HttpTransport(repository) - case "file" => new FileTransport(repository) - case other => throw new IllegalArgumentException(s"Unsupported transport protocol: $other") - } - override def getPriority: Float = 1.0f -} - -/** Aether Http <-> Ivy Http adapter. Aether's is better, but Ivy's has configuration hooks in sbt. */ -class HttpTransport(repository: RemoteRepository) extends AbstractTransporter { - class NotFoundException(msg: String) extends Exception(msg) - private def toURL(task: TransportTask): java.net.URL = - try new java.net.URL(s"${repository.getUrl}/${task.getLocation.toASCIIString}") - catch { - case e: IllegalArgumentException => throw new IllegalArgumentException(s" URL (${task.getLocation}) is not absolute.") - } - private def toResource(task: TransportTask): Resource = new URLResource(toURL(task)) - override def implPeek(peek: PeekTask): Unit = { - if (!toResource(peek).exists()) throw new NotFoundException(s"Could not find ${peek.getLocation}") - } - override def implClose(): Unit = () - override def implGet(out: GetTask): Unit = { - if (!toResource(out).exists()) throw new NotFoundException(s"Could not find ${out.getLocation}") - URLHandlerRegistry.getDefault.download(toURL(out), out.getDataFile, null) - } - override def implPut(put: PutTask): Unit = { - val to = toURL(put) - Option(put.getDataFile) match { - case Some(file) => URLHandlerRegistry.getDefault.upload(file, to, null) - case None => - // TODO - Ivy does not support uploading not from a file. This isn't very efficient in ANY way, - // so if we rewrite the URL handler for Ivy we should fix this as well. - sbt.IO.withTemporaryFile("tmp", "upload") { file => - val in = put.newInputStream() - try sbt.IO.transfer(in, file) - finally in.close() - URLHandlerRegistry.getDefault.upload(file, to, null) - } - } - - } - override def classify(err: Throwable): Int = - err match { - // TODO - Implement - case _: NotFoundException => Transporter.ERROR_NOT_FOUND - case _ => Transporter.ERROR_OTHER - } -} - -class FileTransport(repository: RemoteRepository) extends AbstractTransporter { - class NotFoundException(msg: String) extends Exception(msg) - private def toURL(task: TransportTask): java.net.URL = - try new java.net.URL(s"${repository.getUrl}/${task.getLocation.toASCIIString}") - catch { - case e: IllegalArgumentException => throw new IllegalArgumentException(s" URL (${task.getLocation}) is not absolute.") - } - private def toResource(task: TransportTask): Resource = new URLResource(toURL(task)) - private def toFile(task: TransportTask): java.io.File = - new java.io.File(toURL(task).toURI) - override def implPeek(peek: PeekTask): Unit = { - if (!toFile(peek).exists()) throw new NotFoundException(s"Could not find ${peek.getLocation}") - } - override def implClose(): Unit = () - override def implGet(out: GetTask): Unit = { - val from = toFile(out) - if (!from.exists()) throw new NotFoundException(s"Could not find ${out.getLocation}") - sbt.IO.copyFile(from, out.getDataFile, true) - } - override def implPut(put: PutTask): Unit = { - val to = toFile(put) - Option(put.getDataFile) match { - case Some(from) => - sbt.IO.copyFile(from, to, true) - case None => - // Here it's most likely a SHA or somethign where we read from memory. - val in = put.newInputStream - try sbt.IO.transfer(in, to) - finally in.close() - } - } - override def classify(err: Throwable): Int = - err match { - // TODO - Implement - case _: NotFoundException => Transporter.ERROR_NOT_FOUND - case _ => Transporter.ERROR_OTHER - } -} \ No newline at end of file diff --git a/sbt-maven-resolver/src/main/scala/sbt/MavenResolverConverter.scala b/sbt-maven-resolver/src/main/scala/sbt/MavenResolverConverter.scala index 7f527c813..94274edc9 100644 --- a/sbt-maven-resolver/src/main/scala/sbt/MavenResolverConverter.scala +++ b/sbt-maven-resolver/src/main/scala/sbt/MavenResolverConverter.scala @@ -1,7 +1,7 @@ package sbt import UpdateOptions.ResolverConverter -import org.apache.ivy.plugins.resolver.{ MavenCacheRepositoryResolver, MavenRemoteRepositoryResolver } +import sbt.mavenint.{ MavenCacheRepositoryResolver, MavenRemoteRepositoryResolver } object MavenResolverConverter { val converter: ResolverConverter = { diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/FileTransport.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/FileTransport.scala new file mode 100644 index 000000000..ad0c3b0ee --- /dev/null +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/FileTransport.scala @@ -0,0 +1,48 @@ +package sbt.mavenint + +import org.apache.ivy.plugins.repository.Resource +import org.apache.ivy.plugins.repository.url.URLResource +import org.eclipse.aether.repository.RemoteRepository +import org.eclipse.aether.spi.connector.transport._ + +/** + * A bridge file transportation protocol which uses some Ivy/sbt mechanisms. + */ +class FileTransport(repository: RemoteRepository) extends AbstractTransporter { + class NotFoundException(msg: String) extends Exception(msg) + private def toURL(task: TransportTask): java.net.URL = + try new java.net.URL(s"${repository.getUrl}/${task.getLocation.toASCIIString}") + catch { + case e: IllegalArgumentException => throw new IllegalArgumentException(s" URL (${task.getLocation}) is not absolute.") + } + private def toResource(task: TransportTask): Resource = new URLResource(toURL(task)) + private def toFile(task: TransportTask): java.io.File = + new java.io.File(toURL(task).toURI) + override def implPeek(peek: PeekTask): Unit = { + if (!toFile(peek).exists()) throw new NotFoundException(s"Could not find ${peek.getLocation}") + } + override def implClose(): Unit = () + override def implGet(out: GetTask): Unit = { + val from = toFile(out) + if (!from.exists()) throw new NotFoundException(s"Could not find ${out.getLocation}") + sbt.IO.copyFile(from, out.getDataFile, true) + } + override def implPut(put: PutTask): Unit = { + val to = toFile(put) + Option(put.getDataFile) match { + case Some(from) => + sbt.IO.copyFile(from, to, true) + case None => + // Here it's most likely a SHA or somethign where we read from memory. + val in = put.newInputStream + try sbt.IO.transfer(in, to) + finally in.close() + } + } + override def classify(err: Throwable): Int = + err match { + // TODO - Have we caught enough exceptions here? + case _: NotFoundException => Transporter.ERROR_NOT_FOUND + case _ => Transporter.ERROR_OTHER + } +} diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/HttpTransport.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/HttpTransport.scala new file mode 100644 index 000000000..4ffda4185 --- /dev/null +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/HttpTransport.scala @@ -0,0 +1,47 @@ +package sbt.mavenint + +import org.apache.ivy.plugins.repository.Resource +import org.apache.ivy.plugins.repository.url.URLResource +import org.apache.ivy.util.url.URLHandlerRegistry +import org.eclipse.aether.repository.RemoteRepository +import org.eclipse.aether.spi.connector.transport._ + +/** Aether Http <-> Ivy Http adapter. Aether's is better, but Ivy's has configuration hooks in sbt. */ +class HttpTransport(repository: RemoteRepository) extends AbstractTransporter { + class NotFoundException(msg: String) extends Exception(msg) + private def toURL(task: TransportTask): java.net.URL = + try new java.net.URL(s"${repository.getUrl}/${task.getLocation.toASCIIString}") + catch { + case e: IllegalArgumentException => throw new IllegalArgumentException(s" URL (${task.getLocation}) is not absolute.") + } + private def toResource(task: TransportTask): Resource = new URLResource(toURL(task)) + override def implPeek(peek: PeekTask): Unit = { + if (!toResource(peek).exists()) throw new NotFoundException(s"Could not find ${peek.getLocation}") + } + override def implClose(): Unit = () + override def implGet(out: GetTask): Unit = { + if (!toResource(out).exists()) throw new NotFoundException(s"Could not find ${out.getLocation}") + URLHandlerRegistry.getDefault.download(toURL(out), out.getDataFile, null) + } + override def implPut(put: PutTask): Unit = { + val to = toURL(put) + Option(put.getDataFile) match { + case Some(file) => URLHandlerRegistry.getDefault.upload(file, to, null) + case None => + // TODO - Ivy does not support uploading not from a file. This isn't very efficient in ANY way, + // so if we rewrite the URL handler for Ivy we should fix this as well. + sbt.IO.withTemporaryFile("tmp", "upload") { file => + val in = put.newInputStream() + try sbt.IO.transfer(in, file) + finally in.close() + URLHandlerRegistry.getDefault.upload(file, to, null) + } + } + } + override def classify(err: Throwable): Int = + err match { + // TODO - Have we caught all the important exceptions here. + case _: NotFoundException => Transporter.ERROR_NOT_FOUND + case _ => Transporter.ERROR_OTHER + } +} diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenCacheRepositoryResolver.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenCacheRepositoryResolver.scala new file mode 100644 index 000000000..de8c6d7a3 --- /dev/null +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenCacheRepositoryResolver.scala @@ -0,0 +1,80 @@ +package sbt +package mavenint + +import org.apache.ivy.core.module.id.ModuleRevisionId +import org.apache.ivy.core.settings.IvySettings +import org.eclipse.aether.artifact.{ DefaultArtifact => AetherArtifact } +import org.eclipse.aether.installation.{ InstallRequest => AetherInstallRequest } +import org.eclipse.aether.metadata.{ DefaultMetadata, Metadata } +import org.eclipse.aether.resolution.{ + ArtifactDescriptorRequest => AetherDescriptorRequest, + ArtifactRequest => AetherArtifactRequest, + MetadataRequest => AetherMetadataRequest +} +import sbt.ivyint.CustomMavenResolver + +import scala.collection.JavaConverters._ + +/** + * A resolver instance which can resolve from a maven CACHE. + * + * Note: This should never hit somethign remote, as it just looks in the maven cache for things already resolved. + */ +class MavenCacheRepositoryResolver(val repo: MavenCache, settings: IvySettings) + extends MavenRepositoryResolver(settings) with CustomMavenResolver { + setName(repo.name) + protected val system = MavenRepositorySystemFactory.newRepositorySystemImpl + sbt.IO.createDirectory(repo.rootFile) + protected val session = MavenRepositorySystemFactory.newSessionImpl(system, repo.rootFile) + protected def setRepository(request: AetherMetadataRequest): AetherMetadataRequest = request + protected def addRepositories(request: AetherDescriptorRequest): AetherDescriptorRequest = request + protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest = request + protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit = { + val request = new AetherInstallRequest() + artifacts foreach request.addArtifact + system.install(session, request) + } + // TODO - Share this with non-local repository code, since it's MOSTLY the same. + protected def getPublicationTime(mrid: ModuleRevisionId): Option[Long] = { + val metadataRequest = new AetherMetadataRequest() + metadataRequest.setMetadata( + new DefaultMetadata( + mrid.getOrganisation, + mrid.getName, + mrid.getRevision, + MavenRepositoryResolver.MAVEN_METADATA_XML, + Metadata.Nature.RELEASE_OR_SNAPSHOT)) + val metadataResultOpt = + try system.resolveMetadata(session, java.util.Arrays.asList(metadataRequest)).asScala.headOption + catch { + case e: org.eclipse.aether.resolution.ArtifactResolutionException => None + } + try metadataResultOpt match { + case Some(md) if md.isResolved => + import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader + import org.codehaus.plexus.util.ReaderFactory + val readMetadata = { + val reader = ReaderFactory.newXmlReader(md.getMetadata.getFile) + try new MetadataXpp3Reader().read(reader, false) + finally reader.close() + } + val timestampOpt = + for { + v <- Option(readMetadata.getVersioning) + sp <- Option(v.getSnapshot) + ts <- Option(sp.getTimestamp) + t <- MavenRepositoryResolver.parseTimeString(ts) + } yield t + val lastUpdatedOpt = + for { + v <- Option(readMetadata.getVersioning) + lu <- Option(v.getLastUpdated) + d <- MavenRepositoryResolver.parseTimeString(lu) + } yield d + // TODO - Only look at timestamp *IF* the version is for a snapshot. + timestampOpt orElse lastUpdatedOpt + case _ => None + } + } + override def toString = s"${repo.name}: ${repo.root}" +} diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRemoteRepositoryResolver.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRemoteRepositoryResolver.scala new file mode 100644 index 000000000..1661832b2 --- /dev/null +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRemoteRepositoryResolver.scala @@ -0,0 +1,95 @@ +package sbt +package mavenint + +import org.apache.ivy.core.IvyContext +import org.apache.ivy.core.module.id.ModuleRevisionId +import org.apache.ivy.core.settings.IvySettings +import org.eclipse.aether.artifact.{ DefaultArtifact => AetherArtifact } +import org.eclipse.aether.deployment.{ DeployRequest => AetherDeployRequest } +import org.eclipse.aether.metadata.{ DefaultMetadata, Metadata } +import org.eclipse.aether.resolution.{ + ArtifactDescriptorRequest => AetherDescriptorRequest, + ArtifactDescriptorResult => AetherDescriptorResult, + ArtifactRequest => AetherArtifactRequest, + MetadataRequest => AetherMetadataRequest +} +import sbt.ivyint.CustomRemoteMavenResolver +import scala.collection.JavaConverters._ + +/** + * A resolver instance which can resolve from a REMOTE maven repository. + * + * Note: This creates its *own* local cache directory for cache metadata. using its name. + * + */ +class MavenRemoteRepositoryResolver(val repo: MavenRepository, settings: IvySettings) + extends MavenRepositoryResolver(settings) with CustomRemoteMavenResolver { + setName(repo.name) + override def toString = s"${repo.name}: ${repo.root}" + protected val system = MavenRepositorySystemFactory.newRepositorySystemImpl + // Note: All maven repository resolvers will use the SAME maven cache. + // We're not sure if we care whether or not this means that the wrong resolver may report finding an artifact. + // The key is not to duplicate files repeatedly across many caches. + private val localRepo = new java.io.File(settings.getDefaultIvyUserDir, s"maven-cache") + sbt.IO.createDirectory(localRepo) + protected val session = MavenRepositorySystemFactory.newSessionImpl(system, localRepo) + private val aetherRepository = { + new org.eclipse.aether.repository.RemoteRepository.Builder(repo.name, SbtRepositoryLayout.LAYOUT_NAME, repo.root).build() + } + // TODO - Check if isUseCacheOnly is used correctly. + private def isUseCacheOnly: Boolean = + Option(IvyContext.getContext).flatMap(x => Option(x.getResolveData)).flatMap(x => Option(x.getOptions)).map(_.isUseCacheOnly).getOrElse(false) + protected def addRepositories(request: AetherDescriptorRequest): AetherDescriptorRequest = + if (isUseCacheOnly) request else request.addRepository(aetherRepository) + protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest = + if (isUseCacheOnly) request else request.addRepository(aetherRepository) + /** Actually publishes aether artifacts. */ + protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit = { + val request = new AetherDeployRequest() + request.setRepository(aetherRepository) + artifacts foreach request.addArtifact + system.deploy(session, request) + } + protected def getPublicationTime(mrid: ModuleRevisionId): Option[Long] = { + val metadataRequest = new AetherMetadataRequest() + metadataRequest.setMetadata( + new DefaultMetadata( + mrid.getOrganisation, + mrid.getName, + mrid.getRevision, + MavenRepositoryResolver.MAVEN_METADATA_XML, + Metadata.Nature.RELEASE_OR_SNAPSHOT)) + if (!isUseCacheOnly) metadataRequest.setRepository(aetherRepository) + val metadataResultOpt = + try system.resolveMetadata(session, java.util.Arrays.asList(metadataRequest)).asScala.headOption + catch { + case e: org.eclipse.aether.resolution.ArtifactResolutionException => None + } + try metadataResultOpt match { + case Some(md) if md.isResolved => + import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader + import org.codehaus.plexus.util.ReaderFactory + val readMetadata = { + val reader = ReaderFactory.newXmlReader(md.getMetadata.getFile) + try new MetadataXpp3Reader().read(reader, false) + finally reader.close() + } + val timestampOpt = + for { + v <- Option(readMetadata.getVersioning) + sp <- Option(v.getSnapshot) + ts <- Option(sp.getTimestamp) + t <- MavenRepositoryResolver.parseTimeString(ts) + } yield t + val lastUpdatedOpt = + for { + v <- Option(readMetadata.getVersioning) + lu <- Option(v.getLastUpdated) + d <- MavenRepositoryResolver.parseTimeString(lu) + } yield d + // TODO - Only look at timestamp *IF* the version is for a snapshot. + timestampOpt orElse lastUpdatedOpt + case _ => None + } + } +} diff --git a/sbt-maven-resolver/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositoryResolver.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala similarity index 72% rename from sbt-maven-resolver/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositoryResolver.scala rename to sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala index 183b7f839..b39bfb87a 100644 --- a/sbt-maven-resolver/src/main/scala/org/apache/ivy/plugins/resolver/MavenRepositoryResolver.scala +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositoryResolver.scala @@ -1,48 +1,41 @@ -package org.apache.ivy.plugins.resolver +package sbt.mavenint -import java.io.{ File, IOException } -import java.text.ParseException +import java.io.File import java.util.Date + import org.apache.ivy.core.IvyContext +import org.apache.ivy.core.cache.{ ArtifactOrigin, ModuleDescriptorWriter } import org.apache.ivy.core.module.descriptor._ -import org.apache.ivy.core.module.id.{ ModuleId, ArtifactId, ModuleRevisionId } -import org.apache.ivy.core.report.{ ArtifactDownloadReport, DownloadStatus, MetadataArtifactDownloadReport, DownloadReport } -import org.apache.ivy.core.resolve.{ ResolvedModuleRevision, ResolveData, DownloadOptions } +import org.apache.ivy.core.module.id.{ ModuleId, ModuleRevisionId } +import org.apache.ivy.core.report.{ ArtifactDownloadReport, DownloadReport, DownloadStatus, MetadataArtifactDownloadReport } +import org.apache.ivy.core.resolve.{ DownloadOptions, ResolveData, ResolvedModuleRevision } import org.apache.ivy.core.settings.IvySettings import org.apache.ivy.plugins.matcher.ExactPatternMatcher import org.apache.ivy.plugins.parser.m2.{ PomModuleDescriptorBuilder, ReplaceMavenConfigurationMappings } import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter -import org.apache.ivy.plugins.resolver.MavenRepositoryResolver.JarPackaging +import org.apache.ivy.plugins.resolver.AbstractResolver import org.apache.ivy.plugins.resolver.util.ResolvedResource import org.apache.ivy.util.Message -import org.apache.maven.repository.internal.{ PomExtraDependencyAttributes, SbtRepositoryLayout } -import org.eclipse.aether.{ RepositorySystemSession, RepositorySystem } import org.eclipse.aether.artifact.{ DefaultArtifact => AetherArtifact } -import org.eclipse.aether.metadata.{ Metadata, DefaultMetadata } -import org.eclipse.aether.resolution.{ - ArtifactDescriptorRequest => AetherDescriptorRequest, - ArtifactDescriptorResult => AetherDescriptorResult, - MetadataRequest => AetherMetadataRequest, - ArtifactRequest => AetherArtifactRequest, - ArtifactResolutionException -} import org.eclipse.aether.deployment.{ DeployRequest => AetherDeployRequest } import org.eclipse.aether.installation.{ InstallRequest => AetherInstallRequest } -import org.apache.ivy.core.cache.{ ModuleDescriptorWriter, ArtifactOrigin } -import sbt.{ MavenCache, MavenRepository, SbtExtraProperties } +import org.eclipse.aether.metadata.{ DefaultMetadata, Metadata } +import org.eclipse.aether.resolution.{ ArtifactDescriptorRequest => AetherDescriptorRequest, ArtifactDescriptorResult => AetherDescriptorResult, ArtifactRequest => AetherArtifactRequest, ArtifactResolutionException, MetadataRequest => AetherMetadataRequest } +import org.eclipse.aether.{ RepositorySystem, RepositorySystemSession } import sbt.ivyint.{ CustomMavenResolver, CustomRemoteMavenResolver } +import sbt.mavenint.MavenRepositoryResolver.JarPackaging +import sbt.{ MavenCache, MavenRepository } + import scala.collection.JavaConverters._ object MavenRepositoryResolver { val MAVEN_METADATA_XML = "maven-metadata.xml" val CLASSIFIER_ATTRIBUTE = "e:classifier" - + // TODO - This may be duplciated in more than one location. We need to consolidate. val JarPackagings = Set("eclipse-plugin", "hk2-jar", "orbit", "scala-jar", "jar", "bundle") - object JarPackaging { def unapply(in: String): Boolean = JarPackagings.contains(in) } - // Example: 2014 12 18 09 33 56 val LAST_UPDATE_FORMAT = new java.text.SimpleDateFormat("yyyyMMddhhmmss") def parseTimeString(in: String): Option[Long] = @@ -50,157 +43,15 @@ object MavenRepositoryResolver { catch { case _: java.text.ParseException => None } - val DEFAULT_ARTIFACT_CONFIGURATION = "master" - } /** - * A resolver instance which can resolve from a REMOTE maven repository. - * - * Note: This creates its *own* local cache directory for cache metadata. using its name. + * An abstract repository resolver which has the basic hooks for mapping from Maven (Aether) notions into Ivy notions. * + * THis is used to implement local-cache resolution from ~/.m2 caches or resolving from remote repositories. */ -class MavenRemoteRepositoryResolver(val repo: MavenRepository, settings: IvySettings) - extends AbstractMavenRepositoryResolver(settings) with CustomRemoteMavenResolver { - setName(repo.name) - override def toString = s"${repo.name}: ${repo.root}" - protected val system = MavenRepositorySystemFactory.newRepositorySystemImpl - // Note: All maven repository resolvers will use the SAME maven cache. - // We're not sure if we care whether or not this means that the wrong resolver may report finding an artifact. - // The key is not to duplicate files repeatedly across many caches. - private val localRepo = new java.io.File(settings.getDefaultIvyUserDir, s"maven-cache") - sbt.IO.createDirectory(localRepo) - protected val session = MavenRepositorySystemFactory.newSessionImpl(system, localRepo) - private val aetherRepository = { - new org.eclipse.aether.repository.RemoteRepository.Builder(repo.name, SbtRepositoryLayout.LAYOUT_NAME, repo.root).build() - } - // TODO - Check if isUseCacheOnly is used correctly. - private def isUseCacheOnly: Boolean = - Option(IvyContext.getContext).flatMap(x => Option(x.getResolveData)).flatMap(x => Option(x.getOptions)).map(_.isUseCacheOnly).getOrElse(false) - protected def addRepositories(request: AetherDescriptorRequest): AetherDescriptorRequest = - if (isUseCacheOnly) request else request.addRepository(aetherRepository) - protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest = - if (isUseCacheOnly) request else request.addRepository(aetherRepository) - /** Actually publishes aether artifacts. */ - protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit = { - val request = new AetherDeployRequest() - request.setRepository(aetherRepository) - artifacts foreach request.addArtifact - system.deploy(session, request) - } - protected def getPublicationTime(mrid: ModuleRevisionId): Option[Long] = { - val metadataRequest = new AetherMetadataRequest() - metadataRequest.setMetadata( - new DefaultMetadata( - mrid.getOrganisation, - mrid.getName, - mrid.getRevision, - MavenRepositoryResolver.MAVEN_METADATA_XML, - Metadata.Nature.RELEASE_OR_SNAPSHOT)) - if (!isUseCacheOnly) metadataRequest.setRepository(aetherRepository) - val metadataResultOpt = - try system.resolveMetadata(session, java.util.Arrays.asList(metadataRequest)).asScala.headOption - catch { - case e: org.eclipse.aether.resolution.ArtifactResolutionException => None - } - try metadataResultOpt match { - case Some(md) if md.isResolved => - import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader - import org.codehaus.plexus.util.ReaderFactory - val readMetadata = { - val reader = ReaderFactory.newXmlReader(md.getMetadata.getFile) - try new MetadataXpp3Reader().read(reader, false) - finally reader.close() - } - val timestampOpt = - for { - v <- Option(readMetadata.getVersioning) - sp <- Option(v.getSnapshot) - ts <- Option(sp.getTimestamp) - t <- MavenRepositoryResolver.parseTimeString(ts) - } yield t - val lastUpdatedOpt = - for { - v <- Option(readMetadata.getVersioning) - lu <- Option(v.getLastUpdated) - d <- MavenRepositoryResolver.parseTimeString(lu) - } yield d - // TODO - Only look at timestamp *IF* the version is for a snapshot. - timestampOpt orElse lastUpdatedOpt - case _ => None - } - } -} - -/** - * A resolver instance which can resolve from a maven CACHE. - * - * Note: This should never hit somethign remote, as it just looks in the maven cache for things already resolved. - */ -class MavenCacheRepositoryResolver(val repo: MavenCache, settings: IvySettings) - extends AbstractMavenRepositoryResolver(settings) with CustomMavenResolver { - setName(repo.name) - protected val system = MavenRepositorySystemFactory.newRepositorySystemImpl - sbt.IO.createDirectory(repo.rootFile) - protected val session = MavenRepositorySystemFactory.newSessionImpl(system, repo.rootFile) - protected def setRepository(request: AetherMetadataRequest): AetherMetadataRequest = request - protected def addRepositories(request: AetherDescriptorRequest): AetherDescriptorRequest = request - protected def addRepositories(request: AetherArtifactRequest): AetherArtifactRequest = request - protected def publishArtifacts(artifacts: Seq[AetherArtifact]): Unit = { - val request = new AetherInstallRequest() - artifacts foreach request.addArtifact - system.install(session, request) - } - // TODO - Share this with non-local repository code, since it's MOSTLY the same. - protected def getPublicationTime(mrid: ModuleRevisionId): Option[Long] = { - val metadataRequest = new AetherMetadataRequest() - metadataRequest.setMetadata( - new DefaultMetadata( - mrid.getOrganisation, - mrid.getName, - mrid.getRevision, - MavenRepositoryResolver.MAVEN_METADATA_XML, - Metadata.Nature.RELEASE_OR_SNAPSHOT)) - val metadataResultOpt = - try system.resolveMetadata(session, java.util.Arrays.asList(metadataRequest)).asScala.headOption - catch { - case e: org.eclipse.aether.resolution.ArtifactResolutionException => None - } - try metadataResultOpt match { - case Some(md) if md.isResolved => - import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader - import org.codehaus.plexus.util.ReaderFactory - val readMetadata = { - val reader = ReaderFactory.newXmlReader(md.getMetadata.getFile) - try new MetadataXpp3Reader().read(reader, false) - finally reader.close() - } - val timestampOpt = - for { - v <- Option(readMetadata.getVersioning) - sp <- Option(v.getSnapshot) - ts <- Option(sp.getTimestamp) - t <- MavenRepositoryResolver.parseTimeString(ts) - } yield t - val lastUpdatedOpt = - for { - v <- Option(readMetadata.getVersioning) - lu <- Option(v.getLastUpdated) - d <- MavenRepositoryResolver.parseTimeString(lu) - } yield d - // TODO - Only look at timestamp *IF* the version is for a snapshot. - timestampOpt orElse lastUpdatedOpt - case _ => None - } - } - override def toString = s"${repo.name}: ${repo.root}" -} - -/** An exception we can throw if we encounter issues. */ -class MavenResolutionException(msg: String) extends RuntimeException(msg) {} - -abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends AbstractResolver { +abstract class MavenRepositoryResolver(settings: IvySettings) extends AbstractResolver { /** Our instance of the aether repository system. */ protected val system: RepositorySystem @@ -237,7 +88,7 @@ abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends Ab // Handles appending licenses to the module descriptor fromthe pom. private def addLicenseInfo(md: DefaultModuleDescriptor, map: java.util.Map[String, AnyRef]) = { - val count = map.get(SbtExtraProperties.LICENSE_COUNT_KEY) match { + val count = map.get(SbtPomExtraProperties.LICENSE_COUNT_KEY) match { case null => 0 case x: java.lang.Integer => x.intValue case x: String => x.toInt @@ -245,8 +96,8 @@ abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends Ab } for { i <- 0 until count - name <- Option(map.get(SbtExtraProperties.makeLicenseName(i))).map(_.toString) - url <- Option(map.get(SbtExtraProperties.makeLicenseUrl(i))).map(_.toString) + name <- Option(map.get(SbtPomExtraProperties.makeLicenseName(i))).map(_.toString) + url <- Option(map.get(SbtPomExtraProperties.makeLicenseUrl(i))).map(_.toString) } md.addLicense(new License(name, url)) } @@ -294,7 +145,7 @@ abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends Ab // Here we rip out license info. addLicenseInfo(md, result.getProperties) - md.addExtraInfo(SbtExtraProperties.MAVEN_PACKAGING_KEY, packaging) + md.addExtraInfo(SbtPomExtraProperties.MAVEN_PACKAGING_KEY, packaging) Message.debug(s"Setting publication date to ${new Date(lastModifiedTime)}") // TODO - Figure out the differences between these items. md.setPublicationDate(new Date(lastModifiedTime)) @@ -344,10 +195,10 @@ abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends Ab def getArtifactProperties(dd: ModuleRevisionId): java.util.Map[String, String] = { val m = new java.util.HashMap[String, String] Option(dd.getExtraAttribute(PomExtraDependencyAttributes.ScalaVersionKey)) foreach { sv => - m.put(SbtExtraProperties.POM_SCALA_VERSION, sv) + m.put(SbtPomExtraProperties.POM_SCALA_VERSION, sv) } getSbtVersion(dd) foreach { sv => - m.put(SbtExtraProperties.POM_SBT_VERSION, sv) + m.put(SbtPomExtraProperties.POM_SBT_VERSION, sv) } m } @@ -439,11 +290,6 @@ abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends Ab /** Adds the dependency mediators required based on the managed dependency instances from this pom. */ def addManagedDependenciesFromAether(result: AetherDescriptorResult, md: DefaultModuleDescriptor) { for (d <- result.getManagedDependencies.asScala) { - - if (d.getArtifact.getArtifactId == "stringtemplate") { - Message.warn(s"Found managed stringtemplate in $md !") - } - md.addDependencyDescriptorMediator( ModuleId.newInstance(d.getArtifact.getGroupId, d.getArtifact.getArtifactId), ExactPatternMatcher.INSTANCE, @@ -513,12 +359,7 @@ abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends Ab // TOOD - We may need to fix the configuration mappings here. dd.addDependencyArtifact(optionalizedScope, depArtifact) } - // TODO - is toSystem call correct? md.addDependency(dd) - - if (d.getArtifact.getArtifactId == "stringtemplate") { - Message.warn(s"Found stringtemplate dependency! $dd") - } } } @@ -529,8 +370,8 @@ abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends Ab } private def getPackagingFromPomProperties(props: java.util.Map[String, AnyRef]): String = - if (props.containsKey(SbtExtraProperties.MAVEN_PACKAGING_KEY)) - props.get(SbtExtraProperties.MAVEN_PACKAGING_KEY).toString + if (props.containsKey(SbtPomExtraProperties.MAVEN_PACKAGING_KEY)) + props.get(SbtPomExtraProperties.MAVEN_PACKAGING_KEY).toString else "jar" override def download(artifacts: Array[Artifact], dopts: DownloadOptions): DownloadReport = { @@ -671,7 +512,8 @@ abstract class AbstractMavenRepositoryResolver(settings: IvySettings) extends Ab override def equals(a: Any): Boolean = a match { - case x: AbstractMavenRepositoryResolver => x.getName == getName - case _ => false + case x: MavenRepositoryResolver => x.getName == getName + case _ => false } + override def hashCode: Int = getName.hashCode } diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositorySystemFactory.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositorySystemFactory.scala new file mode 100644 index 000000000..016aa841a --- /dev/null +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenRepositorySystemFactory.scala @@ -0,0 +1,62 @@ +package sbt.mavenint + +import java.io.File + +import org.apache.ivy.plugins.repository.Resource +import org.apache.ivy.plugins.repository.url.URLResource +import org.apache.ivy.util.Message +import org.apache.ivy.util.url.URLHandlerRegistry +import org.apache.maven.repository.internal.{ MavenRepositorySystemUtils, SbtArtifactDescriptorReader, SnapshotMetadataGeneratorFactory, VersionsMetadataGeneratorFactory } +import org.eclipse.aether.{ RepositorySystem, RepositorySystemSession } +import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory +import org.eclipse.aether.impl.{ ArtifactDescriptorReader, DefaultServiceLocator, MetadataGeneratorFactory } +import org.eclipse.aether.repository.{ LocalRepository, RemoteRepository } +import org.eclipse.aether.spi.connector.RepositoryConnectorFactory +import org.eclipse.aether.spi.connector.layout.RepositoryLayoutFactory +import org.eclipse.aether.spi.connector.transport.{ TransporterFactory, _ } + +/** Helper methods for dealing with starting up Aether. */ +object MavenRepositorySystemFactory { + def newRepositorySystemImpl: RepositorySystem = { + // For now we just log Aether instantiation issues. These should probably cause fatal errors. + val locator = MavenRepositorySystemUtils.newServiceLocator() + locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler { + override def serviceCreationFailed(tpe: Class[_], impl: Class[_], exception: Throwable): Unit = { + Message.error(s"Failed to create $tpe, of class $impl") + } + }) + // Here we register the Ivy <-> Aether transport bridge + locator.addService(classOf[TransporterFactory], classOf[MyTransportFactory]) + // This connects the download mechanism to our transports. Why is it needed? no clue. + locator.addService(classOf[RepositoryConnectorFactory], classOf[BasicRepositoryConnectorFactory]) + + // Plugins cause issues here, as their layout is super odd. Here we inject a new plugin layout + locator.addService(classOf[RepositoryLayoutFactory], classOf[SbtPluginLayoutFactory]) + + // Here we add the metadata services so aether will automatically add maven-metadata.xml files. + locator.addService(classOf[MetadataGeneratorFactory], classOf[SnapshotMetadataGeneratorFactory]) + locator.addService(classOf[MetadataGeneratorFactory], classOf[VersionsMetadataGeneratorFactory]) + + // Add our hook for parsing pom.xml files. + locator.setService(classOf[ArtifactDescriptorReader], classOf[SbtArtifactDescriptorReader]) + // Finally, use the DI to create our repository system. + locator.getService(classOf[RepositorySystem]) + } + def newSessionImpl(system: RepositorySystem, localRepoDir: File): RepositorySystemSession = { + val session = MavenRepositorySystemUtils.newSession() + val localRepo = new LocalRepository(localRepoDir) + session setLocalRepositoryManager (system.newLocalRepositoryManager(session, localRepo)) + // Here we set a descriptor policy that FORCES the pom.xml to exist, otherwise Ivy's resolution + // algorithm freaks out. What we could do is also do the ivy lame-thing of checking for a JAR + // instead of a pom.xml, but let's see if this is actually a problem in practice. + val descriptorPolicy = new org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy( + /* ignoreMissing */ false, /* ignoreInvalid. */ true) + session.setArtifactDescriptorPolicy(descriptorPolicy) + session + } + + def defaultLocalRepo: java.io.File = { + new java.io.File(s"${sys.props("user.home")}/.m2/repository") + } +} + diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenResolutionException.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenResolutionException.scala new file mode 100644 index 000000000..3c46d71cb --- /dev/null +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MavenResolutionException.scala @@ -0,0 +1,4 @@ +package sbt.mavenint + +/** An exception we can throw if we encounter issues. */ +class MavenResolutionException(msg: String) extends RuntimeException(msg) {} diff --git a/sbt-maven-resolver/src/main/scala/sbt/mavenint/MyTransportFactory.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MyTransportFactory.scala new file mode 100644 index 000000000..013a48d0a --- /dev/null +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/MyTransportFactory.scala @@ -0,0 +1,16 @@ +package sbt.mavenint + +import org.eclipse.aether.RepositorySystemSession +import org.eclipse.aether.repository.RemoteRepository +import org.eclipse.aether.spi.connector.transport.{ Transporter, TransporterFactory } + +/** Override aether's default transport with Ivy-ones. */ +class MyTransportFactory extends TransporterFactory { + override def newInstance(session: RepositorySystemSession, repository: RemoteRepository): Transporter = + repository.getProtocol match { + case "http" | "https" => new HttpTransport(repository) + case "file" => new FileTransport(repository) + case other => throw new IllegalArgumentException(s"Unsupported transport protocol: $other") + } + override def getPriority: Float = 1.0f +} \ No newline at end of file diff --git a/sbt-maven-resolver/src/main/scala/org/apache/maven/repository/internal/SbtRepositoryLayout.scala b/sbt-maven-resolver/src/main/scala/sbt/mavenint/SbtRepositoryLayout.scala similarity index 87% rename from sbt-maven-resolver/src/main/scala/org/apache/maven/repository/internal/SbtRepositoryLayout.scala rename to sbt-maven-resolver/src/main/scala/sbt/mavenint/SbtRepositoryLayout.scala index 412ba690f..e15fecb1f 100644 --- a/sbt-maven-resolver/src/main/scala/org/apache/maven/repository/internal/SbtRepositoryLayout.scala +++ b/sbt-maven-resolver/src/main/scala/sbt/mavenint/SbtRepositoryLayout.scala @@ -1,15 +1,14 @@ -package org.apache.maven.repository.internal +package sbt.mavenint -import org.apache.ivy.util.Message -import org.eclipse.aether.spi.connector.layout.{ RepositoryLayout, RepositoryLayoutFactory } -import org.eclipse.aether.RepositorySystemSession -import org.eclipse.aether.repository.RemoteRepository -import org.eclipse.aether.transfer.NoRepositoryLayoutException -import org.eclipse.aether.metadata.Metadata -import org.eclipse.aether.spi.connector.layout.RepositoryLayout.Checksum -import org.eclipse.aether.artifact.Artifact import java.net.URI -import sbt.SbtExtraProperties + +import org.eclipse.aether.RepositorySystemSession +import org.eclipse.aether.artifact.Artifact +import org.eclipse.aether.metadata.Metadata +import org.eclipse.aether.repository.RemoteRepository +import org.eclipse.aether.spi.connector.layout.RepositoryLayout.Checksum +import org.eclipse.aether.spi.connector.layout.{ RepositoryLayout, RepositoryLayoutFactory } +import org.eclipse.aether.transfer.NoRepositoryLayoutException import scala.util.matching.Regex @@ -32,15 +31,13 @@ object SbtRepositoryLayout extends RepositoryLayout { // get location is ALMOST the same for Metadata + artifact... but subtle differences are important. def getLocation(artifact: Artifact, upload: Boolean): URI = { - import collection.JavaConverters._ - val sbtVersion = Option(artifact.getProperties.get(SbtExtraProperties.POM_SBT_VERSION)) - val scalaVersion = Option(artifact.getProperties.get(SbtExtraProperties.POM_SCALA_VERSION)) + val sbtVersion = Option(artifact.getProperties.get(SbtPomExtraProperties.POM_SBT_VERSION)) + val scalaVersion = Option(artifact.getProperties.get(SbtPomExtraProperties.POM_SCALA_VERSION)) val path = new StringBuilder(128) path.append(artifact.getGroupId.replace('.', '/')).append('/') (sbtVersion zip scalaVersion).headOption match { case Some((sbt, scala)) => if (artifact.getArtifactId contains "_sbt_") { - // TODO - Write this handler. val SbtNameVersionSplit(name, sbt2) = artifact.getArtifactId path.append(name).append('_').append(scala).append('_').append(sbt).append('/') } else path.append(artifact.getArtifactId).append('_').append(scala).append('_').append(sbt).append('/') @@ -69,8 +66,8 @@ object SbtRepositoryLayout extends RepositoryLayout { val SbtNameVersionSplit = new Regex("(.*)_sbt_(.*)") def getLocation(metadata: Metadata, upload: Boolean): URI = { - val sbtVersion = Option(metadata.getProperties.get(SbtExtraProperties.POM_SBT_VERSION)) - val scalaVersion = Option(metadata.getProperties.get(SbtExtraProperties.POM_SCALA_VERSION)) + val sbtVersion = Option(metadata.getProperties.get(SbtPomExtraProperties.POM_SBT_VERSION)) + val scalaVersion = Option(metadata.getProperties.get(SbtPomExtraProperties.POM_SCALA_VERSION)) val path = new StringBuilder(128) path.append(metadata.getGroupId.replace('.', '/')).append('/') (sbtVersion zip scalaVersion).headOption match { @@ -78,7 +75,6 @@ object SbtRepositoryLayout extends RepositoryLayout { if (metadata.getArtifactId contains "_sbt_") { val SbtNameVersionSplit(name, sbt2) = metadata.getArtifactId path.append(name).append('_').append(scala).append('_').append(sbt).append('/') - // TODO - Write this handler. } else path.append(metadata.getArtifactId).append('_').append(scala).append('_').append(sbt).append('/') case None => // TODO - Should we automatically append the _ here? Proabbly not for now. diff --git a/sbt-maven-resolver/src/test/scala/MavenResolutionSpec.scala b/sbt-maven-resolver/src/test/scala/MavenResolutionSpec.scala index 1013fc80b..1262cf7d1 100644 --- a/sbt-maven-resolver/src/test/scala/MavenResolutionSpec.scala +++ b/sbt-maven-resolver/src/test/scala/MavenResolutionSpec.scala @@ -1,9 +1,8 @@ package sbt import java.io.FileInputStream - -import org.apache.maven.repository.internal.PomExtraDependencyAttributes import org.specs2._ +import sbt.mavenint.PomExtraDependencyAttributes class MavenResolutionSpec extends BaseIvySpecification { def is = args(sequential = true) ^ s2""".stripMargin